/* SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0)
 *
 * Copyright 2008-2016 Freescale Semiconductor Inc.
 * Copyright 2016 NXP
 *
 */

#ifndef __RTA_OPERATION_CMD_H__
#define __RTA_OPERATION_CMD_H__

#if defined(RTE_TOOLCHAIN_GCC) && (GCC_VERSION >= 70000)
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
#endif

extern enum rta_sec_era rta_sec_era;

static inline int
__rta_alg_aai_aes(uint16_t aai)
{
	uint16_t aes_mode = aai & OP_ALG_AESA_MODE_MASK;

	if (aai & OP_ALG_AAI_C2K) {
		if (rta_sec_era < RTA_SEC_ERA_5)
			return -1;
		if ((aes_mode != OP_ALG_AAI_CCM) &&
		    (aes_mode != OP_ALG_AAI_GCM))
			return -EINVAL;
	}

	switch (aes_mode) {
	case OP_ALG_AAI_CBC_CMAC:
	case OP_ALG_AAI_CTR_CMAC_LTE:
	case OP_ALG_AAI_CTR_CMAC:
		if (rta_sec_era < RTA_SEC_ERA_2)
			return -EINVAL;
		/* no break */
	case OP_ALG_AAI_CTR:
	case OP_ALG_AAI_CBC:
	case OP_ALG_AAI_ECB:
	case OP_ALG_AAI_OFB:
	case OP_ALG_AAI_CFB:
	case OP_ALG_AAI_XTS:
	case OP_ALG_AAI_CMAC:
	case OP_ALG_AAI_XCBC_MAC:
	case OP_ALG_AAI_CCM:
	case OP_ALG_AAI_GCM:
	case OP_ALG_AAI_CBC_XCBCMAC:
	case OP_ALG_AAI_CTR_XCBCMAC:
		return 0;
	}

	return -EINVAL;
}

static inline int
__rta_alg_aai_des(uint16_t aai)
{
	uint16_t aai_code = (uint16_t)(aai & ~OP_ALG_AAI_CHECKODD);

	switch (aai_code) {
	case OP_ALG_AAI_CBC:
	case OP_ALG_AAI_ECB:
	case OP_ALG_AAI_CFB:
	case OP_ALG_AAI_OFB:
		return 0;
	}

	return -EINVAL;
}

static inline int
__rta_alg_aai_md5(uint16_t aai)
{
	switch (aai) {
	case OP_ALG_AAI_HMAC:
		if (rta_sec_era < RTA_SEC_ERA_2)
			return -EINVAL;
		/* no break */
	case OP_ALG_AAI_SMAC:
	case OP_ALG_AAI_HASH:
	case OP_ALG_AAI_HMAC_PRECOMP:
		return 0;
	}

	return -EINVAL;
}

static inline int
__rta_alg_aai_sha(uint16_t aai)
{
	switch (aai) {
	case OP_ALG_AAI_HMAC:
		if (rta_sec_era < RTA_SEC_ERA_2)
			return -EINVAL;
		/* no break */
	case OP_ALG_AAI_HASH:
	case OP_ALG_AAI_HMAC_PRECOMP:
		return 0;
	}

	return -EINVAL;
}

static inline int
__rta_alg_aai_rng(uint16_t aai)
{
	uint16_t rng_mode = aai & OP_ALG_RNG_MODE_MASK;
	uint16_t rng_sh = aai & OP_ALG_AAI_RNG4_SH_MASK;

	switch (rng_mode) {
	case OP_ALG_AAI_RNG:
	case OP_ALG_AAI_RNG_NZB:
	case OP_ALG_AAI_RNG_OBP:
		break;
	default:
		return -EINVAL;
	}

	/* State Handle bits are valid only for SEC Era >= 5 */
	if ((rta_sec_era < RTA_SEC_ERA_5) && rng_sh)
		return -EINVAL;

	/* PS, AI, SK bits are also valid only for SEC Era >= 5 */
	if ((rta_sec_era < RTA_SEC_ERA_5) && (aai &
	     (OP_ALG_AAI_RNG4_PS | OP_ALG_AAI_RNG4_AI | OP_ALG_AAI_RNG4_SK)))
		return -EINVAL;

	switch (rng_sh) {
	case OP_ALG_AAI_RNG4_SH_0:
	case OP_ALG_AAI_RNG4_SH_1:
		return 0;
	}

	return -EINVAL;
}

static inline int
__rta_alg_aai_crc(uint16_t aai)
{
	uint16_t aai_code = aai & OP_ALG_CRC_POLY_MASK;

	switch (aai_code) {
	case OP_ALG_AAI_802:
	case OP_ALG_AAI_3385:
	case OP_ALG_AAI_CUST_POLY:
		return 0;
	}

	return -EINVAL;
}

static inline int
__rta_alg_aai_kasumi(uint16_t aai)
{
	switch (aai) {
	case OP_ALG_AAI_GSM:
	case OP_ALG_AAI_EDGE:
	case OP_ALG_AAI_F8:
	case OP_ALG_AAI_F9:
		return 0;
	}

	return -EINVAL;
}

static inline int
__rta_alg_aai_snow_f9(uint16_t aai)
{
	if (aai == OP_ALG_AAI_F9)
		return 0;

	return -EINVAL;
}

static inline int
__rta_alg_aai_snow_f8(uint16_t aai)
{
	if (aai == OP_ALG_AAI_F8)
		return 0;

	return -EINVAL;
}

static inline int
__rta_alg_aai_zuce(uint16_t aai)
{
	if (aai == OP_ALG_AAI_F8)
		return 0;

	return -EINVAL;
}

static inline int
__rta_alg_aai_zuca(uint16_t aai)
{
	if (aai == OP_ALG_AAI_F9)
		return 0;

	return -EINVAL;
}

struct alg_aai_map {
	uint32_t chipher_algo;
	int (*aai_func)(uint16_t);
	uint32_t class;
};

static const struct alg_aai_map alg_table[] = {
/*1*/	{ OP_ALG_ALGSEL_AES,      __rta_alg_aai_aes,    OP_TYPE_CLASS1_ALG },
	{ OP_ALG_ALGSEL_DES,      __rta_alg_aai_des,    OP_TYPE_CLASS1_ALG },
	{ OP_ALG_ALGSEL_3DES,     __rta_alg_aai_des,    OP_TYPE_CLASS1_ALG },
	{ OP_ALG_ALGSEL_MD5,      __rta_alg_aai_md5,    OP_TYPE_CLASS2_ALG },
	{ OP_ALG_ALGSEL_SHA1,     __rta_alg_aai_md5,    OP_TYPE_CLASS2_ALG },
	{ OP_ALG_ALGSEL_SHA224,   __rta_alg_aai_sha,    OP_TYPE_CLASS2_ALG },
	{ OP_ALG_ALGSEL_SHA256,   __rta_alg_aai_sha,    OP_TYPE_CLASS2_ALG },
	{ OP_ALG_ALGSEL_SHA384,   __rta_alg_aai_sha,    OP_TYPE_CLASS2_ALG },
	{ OP_ALG_ALGSEL_SHA512,   __rta_alg_aai_sha,    OP_TYPE_CLASS2_ALG },
	{ OP_ALG_ALGSEL_RNG,      __rta_alg_aai_rng,    OP_TYPE_CLASS1_ALG },
/*11*/	{ OP_ALG_ALGSEL_CRC,      __rta_alg_aai_crc,    OP_TYPE_CLASS2_ALG },
	{ OP_ALG_ALGSEL_ARC4,     NULL,                 OP_TYPE_CLASS1_ALG },
	{ OP_ALG_ALGSEL_SNOW_F8,  __rta_alg_aai_snow_f8, OP_TYPE_CLASS1_ALG },
/*14*/	{ OP_ALG_ALGSEL_KASUMI,   __rta_alg_aai_kasumi, OP_TYPE_CLASS1_ALG },
	{ OP_ALG_ALGSEL_SNOW_F9,  __rta_alg_aai_snow_f9, OP_TYPE_CLASS2_ALG },
	{ OP_ALG_ALGSEL_ZUCE,     __rta_alg_aai_zuce,   OP_TYPE_CLASS1_ALG },
/*17*/	{ OP_ALG_ALGSEL_ZUCA,     __rta_alg_aai_zuca,   OP_TYPE_CLASS2_ALG }
};

/*
 * Allowed OPERATION algorithms for each SEC Era.
 * Values represent the number of entries from alg_table[] that are supported.
 */
static const unsigned int alg_table_sz[] = {14, 15, 15, 15, 17, 17, 11, 17};

static inline int
rta_operation(struct program *program, uint32_t cipher_algo,
	      uint16_t aai, uint8_t algo_state,
	      int icv_checking, int enc)
{
	uint32_t opcode = CMD_OPERATION;
	unsigned int i, found = 0;
	unsigned int start_pc = program->current_pc;
	int ret;

	for (i = 0; i < alg_table_sz[rta_sec_era]; i++) {
		if (alg_table[i].chipher_algo == cipher_algo) {
			opcode |= cipher_algo | alg_table[i].class;
			/* nothing else to verify */
			if (alg_table[i].aai_func == NULL) {
				found = 1;
				break;
			}

			aai &= OP_ALG_AAI_MASK;

			ret = (*alg_table[i].aai_func)(aai);
			if (ret < 0) {
				pr_err("OPERATION: Bad AAI Type. SEC Program Line: %d\n",
				       program->current_pc);
				goto err;
			}
			opcode |= aai;
			found = 1;
			break;
		}
	}
	if (!found) {
		pr_err("OPERATION: Invalid Command. SEC Program Line: %d\n",
		       program->current_pc);
		ret = -EINVAL;
		goto err;
	}

	switch (algo_state) {
	case OP_ALG_AS_UPDATE:
	case OP_ALG_AS_INIT:
	case OP_ALG_AS_FINALIZE:
	case OP_ALG_AS_INITFINAL:
		opcode |= algo_state;
		break;
	default:
		pr_err("Invalid Operation Command\n");
		ret = -EINVAL;
		goto err;
	}

	switch (icv_checking) {
	case ICV_CHECK_DISABLE:
		/*
		 * opcode |= OP_ALG_ICV_OFF;
		 * OP_ALG_ICV_OFF is 0
		 */
		break;
	case ICV_CHECK_ENABLE:
		opcode |= OP_ALG_ICV_ON;
		break;
	default:
		pr_err("Invalid Operation Command\n");
		ret = -EINVAL;
		goto err;
	}

	switch (enc) {
	case DIR_DEC:
		/*
		 * opcode |= OP_ALG_DECRYPT;
		 * OP_ALG_DECRYPT is 0
		 */
		break;
	case DIR_ENC:
		opcode |= OP_ALG_ENCRYPT;
		break;
	default:
		pr_err("Invalid Operation Command\n");
		ret = -EINVAL;
		goto err;
	}

	__rta_out32(program, opcode);
	program->current_instruction++;
	return (int)start_pc;

 err:
	program->first_error_pc = start_pc;
	return ret;
}

/*
 * OPERATION PKHA routines
 */
static inline int
__rta_pkha_clearmem(uint32_t pkha_op)
{
	switch (pkha_op) {
	case (OP_ALG_PKMODE_CLEARMEM_ALL):
	case (OP_ALG_PKMODE_CLEARMEM_ABE):
	case (OP_ALG_PKMODE_CLEARMEM_ABN):
	case (OP_ALG_PKMODE_CLEARMEM_AB):
	case (OP_ALG_PKMODE_CLEARMEM_AEN):
	case (OP_ALG_PKMODE_CLEARMEM_AE):
	case (OP_ALG_PKMODE_CLEARMEM_AN):
	case (OP_ALG_PKMODE_CLEARMEM_A):
	case (OP_ALG_PKMODE_CLEARMEM_BEN):
	case (OP_ALG_PKMODE_CLEARMEM_BE):
	case (OP_ALG_PKMODE_CLEARMEM_BN):
	case (OP_ALG_PKMODE_CLEARMEM_B):
	case (OP_ALG_PKMODE_CLEARMEM_EN):
	case (OP_ALG_PKMODE_CLEARMEM_N):
	case (OP_ALG_PKMODE_CLEARMEM_E):
		return 0;
	}

	return -EINVAL;
}

static inline int
__rta_pkha_mod_arithmetic(uint32_t pkha_op)
{
	pkha_op &= (uint32_t)~OP_ALG_PKMODE_OUT_A;

	switch (pkha_op) {
	case (OP_ALG_PKMODE_MOD_ADD):
	case (OP_ALG_PKMODE_MOD_SUB_AB):
	case (OP_ALG_PKMODE_MOD_SUB_BA):
	case (OP_ALG_PKMODE_MOD_MULT):
	case (OP_ALG_PKMODE_MOD_MULT_IM):
	case (OP_ALG_PKMODE_MOD_MULT_IM_OM):
	case (OP_ALG_PKMODE_MOD_EXPO):
	case (OP_ALG_PKMODE_MOD_EXPO_TEQ):
	case (OP_ALG_PKMODE_MOD_EXPO_IM):
	case (OP_ALG_PKMODE_MOD_EXPO_IM_TEQ):
	case (OP_ALG_PKMODE_MOD_REDUCT):
	case (OP_ALG_PKMODE_MOD_INV):
	case (OP_ALG_PKMODE_MOD_MONT_CNST):
	case (OP_ALG_PKMODE_MOD_CRT_CNST):
	case (OP_ALG_PKMODE_MOD_GCD):
	case (OP_ALG_PKMODE_MOD_PRIMALITY):
	case (OP_ALG_PKMODE_MOD_SML_EXP):
	case (OP_ALG_PKMODE_F2M_ADD):
	case (OP_ALG_PKMODE_F2M_MUL):
	case (OP_ALG_PKMODE_F2M_MUL_IM):
	case (OP_ALG_PKMODE_F2M_MUL_IM_OM):
	case (OP_ALG_PKMODE_F2M_EXP):
	case (OP_ALG_PKMODE_F2M_EXP_TEQ):
	case (OP_ALG_PKMODE_F2M_AMODN):
	case (OP_ALG_PKMODE_F2M_INV):
	case (OP_ALG_PKMODE_F2M_R2):
	case (OP_ALG_PKMODE_F2M_GCD):
	case (OP_ALG_PKMODE_F2M_SML_EXP):
	case (OP_ALG_PKMODE_ECC_F2M_ADD):
	case (OP_ALG_PKMODE_ECC_F2M_ADD_IM_OM_PROJ):
	case (OP_ALG_PKMODE_ECC_F2M_DBL):
	case (OP_ALG_PKMODE_ECC_F2M_DBL_IM_OM_PROJ):
	case (OP_ALG_PKMODE_ECC_F2M_MUL):
	case (OP_ALG_PKMODE_ECC_F2M_MUL_TEQ):
	case (OP_ALG_PKMODE_ECC_F2M_MUL_R2):
	case (OP_ALG_PKMODE_ECC_F2M_MUL_R2_TEQ):
	case (OP_ALG_PKMODE_ECC_F2M_MUL_R2_PROJ):
	case (OP_ALG_PKMODE_ECC_F2M_MUL_R2_PROJ_TEQ):
	case (OP_ALG_PKMODE_ECC_MOD_ADD):
	case (OP_ALG_PKMODE_ECC_MOD_ADD_IM_OM_PROJ):
	case (OP_ALG_PKMODE_ECC_MOD_DBL):
	case (OP_ALG_PKMODE_ECC_MOD_DBL_IM_OM_PROJ):
	case (OP_ALG_PKMODE_ECC_MOD_MUL):
	case (OP_ALG_PKMODE_ECC_MOD_MUL_TEQ):
	case (OP_ALG_PKMODE_ECC_MOD_MUL_R2):
	case (OP_ALG_PKMODE_ECC_MOD_MUL_R2_TEQ):
	case (OP_ALG_PKMODE_ECC_MOD_MUL_R2_PROJ):
	case (OP_ALG_PKMODE_ECC_MOD_MUL_R2_PROJ_TEQ):
		return 0;
	}

	return -EINVAL;
}

static inline int
__rta_pkha_copymem(uint32_t pkha_op)
{
	switch (pkha_op) {
	case (OP_ALG_PKMODE_COPY_NSZ_A0_B0):
	case (OP_ALG_PKMODE_COPY_NSZ_A0_B1):
	case (OP_ALG_PKMODE_COPY_NSZ_A0_B2):
	case (OP_ALG_PKMODE_COPY_NSZ_A0_B3):
	case (OP_ALG_PKMODE_COPY_NSZ_A1_B0):
	case (OP_ALG_PKMODE_COPY_NSZ_A1_B1):
	case (OP_ALG_PKMODE_COPY_NSZ_A1_B2):
	case (OP_ALG_PKMODE_COPY_NSZ_A1_B3):
	case (OP_ALG_PKMODE_COPY_NSZ_A2_B0):
	case (OP_ALG_PKMODE_COPY_NSZ_A2_B1):
	case (OP_ALG_PKMODE_COPY_NSZ_A2_B2):
	case (OP_ALG_PKMODE_COPY_NSZ_A2_B3):
	case (OP_ALG_PKMODE_COPY_NSZ_A3_B0):
	case (OP_ALG_PKMODE_COPY_NSZ_A3_B1):
	case (OP_ALG_PKMODE_COPY_NSZ_A3_B2):
	case (OP_ALG_PKMODE_COPY_NSZ_A3_B3):
	case (OP_ALG_PKMODE_COPY_NSZ_B0_A0):
	case (OP_ALG_PKMODE_COPY_NSZ_B0_A1):
	case (OP_ALG_PKMODE_COPY_NSZ_B0_A2):
	case (OP_ALG_PKMODE_COPY_NSZ_B0_A3):
	case (OP_ALG_PKMODE_COPY_NSZ_B1_A0):
	case (OP_ALG_PKMODE_COPY_NSZ_B1_A1):
	case (OP_ALG_PKMODE_COPY_NSZ_B1_A2):
	case (OP_ALG_PKMODE_COPY_NSZ_B1_A3):
	case (OP_ALG_PKMODE_COPY_NSZ_B2_A0):
	case (OP_ALG_PKMODE_COPY_NSZ_B2_A1):
	case (OP_ALG_PKMODE_COPY_NSZ_B2_A2):
	case (OP_ALG_PKMODE_COPY_NSZ_B2_A3):
	case (OP_ALG_PKMODE_COPY_NSZ_B3_A0):
	case (OP_ALG_PKMODE_COPY_NSZ_B3_A1):
	case (OP_ALG_PKMODE_COPY_NSZ_B3_A2):
	case (OP_ALG_PKMODE_COPY_NSZ_B3_A3):
	case (OP_ALG_PKMODE_COPY_NSZ_A_E):
	case (OP_ALG_PKMODE_COPY_NSZ_A_N):
	case (OP_ALG_PKMODE_COPY_NSZ_B_E):
	case (OP_ALG_PKMODE_COPY_NSZ_B_N):
	case (OP_ALG_PKMODE_COPY_NSZ_N_A):
	case (OP_ALG_PKMODE_COPY_NSZ_N_B):
	case (OP_ALG_PKMODE_COPY_NSZ_N_E):
	case (OP_ALG_PKMODE_COPY_SSZ_A0_B0):
	case (OP_ALG_PKMODE_COPY_SSZ_A0_B1):
	case (OP_ALG_PKMODE_COPY_SSZ_A0_B2):
	case (OP_ALG_PKMODE_COPY_SSZ_A0_B3):
	case (OP_ALG_PKMODE_COPY_SSZ_A1_B0):
	case (OP_ALG_PKMODE_COPY_SSZ_A1_B1):
	case (OP_ALG_PKMODE_COPY_SSZ_A1_B2):
	case (OP_ALG_PKMODE_COPY_SSZ_A1_B3):
	case (OP_ALG_PKMODE_COPY_SSZ_A2_B0):
	case (OP_ALG_PKMODE_COPY_SSZ_A2_B1):
	case (OP_ALG_PKMODE_COPY_SSZ_A2_B2):
	case (OP_ALG_PKMODE_COPY_SSZ_A2_B3):
	case (OP_ALG_PKMODE_COPY_SSZ_A3_B0):
	case (OP_ALG_PKMODE_COPY_SSZ_A3_B1):
	case (OP_ALG_PKMODE_COPY_SSZ_A3_B2):
	case (OP_ALG_PKMODE_COPY_SSZ_A3_B3):
	case (OP_ALG_PKMODE_COPY_SSZ_B0_A0):
	case (OP_ALG_PKMODE_COPY_SSZ_B0_A1):
	case (OP_ALG_PKMODE_COPY_SSZ_B0_A2):
	case (OP_ALG_PKMODE_COPY_SSZ_B0_A3):
	case (OP_ALG_PKMODE_COPY_SSZ_B1_A0):
	case (OP_ALG_PKMODE_COPY_SSZ_B1_A1):
	case (OP_ALG_PKMODE_COPY_SSZ_B1_A2):
	case (OP_ALG_PKMODE_COPY_SSZ_B1_A3):
	case (OP_ALG_PKMODE_COPY_SSZ_B2_A0):
	case (OP_ALG_PKMODE_COPY_SSZ_B2_A1):
	case (OP_ALG_PKMODE_COPY_SSZ_B2_A2):
	case (OP_ALG_PKMODE_COPY_SSZ_B2_A3):
	case (OP_ALG_PKMODE_COPY_SSZ_B3_A0):
	case (OP_ALG_PKMODE_COPY_SSZ_B3_A1):
	case (OP_ALG_PKMODE_COPY_SSZ_B3_A2):
	case (OP_ALG_PKMODE_COPY_SSZ_B3_A3):
	case (OP_ALG_PKMODE_COPY_SSZ_A_E):
	case (OP_ALG_PKMODE_COPY_SSZ_A_N):
	case (OP_ALG_PKMODE_COPY_SSZ_B_E):
	case (OP_ALG_PKMODE_COPY_SSZ_B_N):
	case (OP_ALG_PKMODE_COPY_SSZ_N_A):
	case (OP_ALG_PKMODE_COPY_SSZ_N_B):
	case (OP_ALG_PKMODE_COPY_SSZ_N_E):
		return 0;
	}

	return -EINVAL;
}

static inline int
rta_pkha_operation(struct program *program, uint32_t op_pkha)
{
	uint32_t opcode = CMD_OPERATION | OP_TYPE_PK | OP_ALG_PK;
	uint32_t pkha_func;
	unsigned int start_pc = program->current_pc;
	int ret = -EINVAL;

	pkha_func = op_pkha & OP_ALG_PK_FUN_MASK;

	switch (pkha_func) {
	case (OP_ALG_PKMODE_CLEARMEM):
		ret = __rta_pkha_clearmem(op_pkha);
		if (ret < 0) {
			pr_err("OPERATION PKHA: Type not supported. SEC Program Line: %d\n",
			       program->current_pc);
			goto err;
		}
		break;
	case (OP_ALG_PKMODE_MOD_ADD):
	case (OP_ALG_PKMODE_MOD_SUB_AB):
	case (OP_ALG_PKMODE_MOD_SUB_BA):
	case (OP_ALG_PKMODE_MOD_MULT):
	case (OP_ALG_PKMODE_MOD_EXPO):
	case (OP_ALG_PKMODE_MOD_REDUCT):
	case (OP_ALG_PKMODE_MOD_INV):
	case (OP_ALG_PKMODE_MOD_MONT_CNST):
	case (OP_ALG_PKMODE_MOD_CRT_CNST):
	case (OP_ALG_PKMODE_MOD_GCD):
	case (OP_ALG_PKMODE_MOD_PRIMALITY):
	case (OP_ALG_PKMODE_MOD_SML_EXP):
	case (OP_ALG_PKMODE_ECC_MOD_ADD):
	case (OP_ALG_PKMODE_ECC_MOD_DBL):
	case (OP_ALG_PKMODE_ECC_MOD_MUL):
		ret = __rta_pkha_mod_arithmetic(op_pkha);
		if (ret < 0) {
			pr_err("OPERATION PKHA: Type not supported. SEC Program Line: %d\n",
			       program->current_pc);
			goto err;
		}
		break;
	case (OP_ALG_PKMODE_COPY_NSZ):
	case (OP_ALG_PKMODE_COPY_SSZ):
		ret = __rta_pkha_copymem(op_pkha);
		if (ret < 0) {
			pr_err("OPERATION PKHA: Type not supported. SEC Program Line: %d\n",
			       program->current_pc);
			goto err;
		}
		break;
	default:
		pr_err("Invalid Operation Command\n");
		goto err;
	}

	opcode |= op_pkha;

	__rta_out32(program, opcode);
	program->current_instruction++;
	return (int)start_pc;

 err:
	program->first_error_pc = start_pc;
	program->current_instruction++;
	return ret;
}

#endif /* __RTA_OPERATION_CMD_H__ */
