/*
 * User Space library to access the Security hardware
 * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

/*!
 * @file lib/sahara2/fsl_shw_sym.c
 *
 * This file implements Symmetric Cipher functions of the FSL SHW API for
 * Sahara.  This does not include CCM.
 */

#include "sahara.h"
#include "fsl_platform.h"
#include "sf_util.h"
#include "adaptor.h"

#ifdef LINUX_KERNEL
EXPORT_SYMBOL(fsl_shw_symmetric_encrypt);
EXPORT_SYMBOL(fsl_shw_symmetric_decrypt);
#endif

#if defined(NEED_CTR_WORKAROUND)
/* CTR mode needs block-multiple data in/out */
#define LENGTH_PATCH  (sym_ctx->block_size_bytes)
#define LENGTH_PATCH_MASK  (sym_ctx->block_size_bytes-1)
#else
#define LENGTH_PATCH  0
#define LENGTH_PATCH_MASK  0	/* du not use! */
#endif

/*!
 * Block of zeroes which is maximum Symmetric block size, used for
 * initializing context register, etc.
 */
static uint32_t block_zeros[4] = {
	0, 0, 0, 0
};

typedef enum cipher_direction {
	SYM_DECRYPT,
	SYM_ENCRYPT
} cipher_direction_t;

/*!
 * Create and run the chain for a symmetric-key operation.
 *
 * @param user_ctx    Who the user is
 * @param key_info    What key is to be used
 * @param sym_ctx     Info details about algorithm
 * @param encrypt     0 = decrypt, non-zero = encrypt
 * @param length      Number of octets at @a in and @a out
 * @param in          Pointer to input data
 * @param out         Location to store output data
 *
 * @return         The status of handing chain to driver,
 *                 or an earlier argument/flag or allocation
 *                 error.
 */
static fsl_shw_return_t do_symmetric(fsl_shw_uco_t * user_ctx,
				     fsl_shw_sko_t * key_info,
				     fsl_shw_scco_t * sym_ctx,
				     cipher_direction_t encrypt,
				     uint32_t length,
				     const uint8_t * in, uint8_t * out)
{
	SAH_SF_DCLS;
	uint8_t *sink = NULL;
	sah_Link *link1 = NULL;
	sah_Link *link2 = NULL;
	sah_Oct_Str ptr1;
	uint32_t size1 = sym_ctx->block_size_bytes;

	SAH_SF_USER_CHECK();

	/* Two different sets of chains, depending on algorithm */
	if (key_info->algorithm == FSL_KEY_ALG_ARC4) {
		if (sym_ctx->flags & FSL_SYM_CTX_INIT) {
			/* Desc. #35 w/ARC4 - start from key */
			header = SAH_HDR_ARC4_SET_MODE_KEY
			    ^ sah_insert_skha_algorithm_arc4;

			DESC_IN_KEY(header, 0, NULL, key_info);
		} else {	/* load SBox */
			/* Desc. #33 w/ARC4 and NO PERMUTE */
			header = SAH_HDR_ARC4_SET_MODE_SBOX
			    ^ sah_insert_skha_no_permute
			    ^ sah_insert_skha_algorithm_arc4;
			DESC_IN_IN(header, 256, sym_ctx->context,
				   3, sym_ctx->context + 256);
		}		/* load SBox */

		/* Add in-out data descriptor to process the data */
		if (length != 0) {
			DESC_IN_OUT(SAH_HDR_SKHA_ENC_DEC, length, in, length,
				    out);
		}

		/* Operation is done ... save what came out? */
		if (sym_ctx->flags & FSL_SYM_CTX_SAVE) {
			/* Desc. #34 - Read SBox, pointers */
			header = SAH_HDR_ARC4_READ_SBOX;
			DESC_OUT_OUT(header, 256, sym_ctx->context,
				     3, sym_ctx->context + 256);
		}
	} else {		/* not ARC4 */
		/* Doing 1- or 2- descriptor chain. */
		/* Desc. #1 and algorithm and mode */
		header = SAH_HDR_SKHA_SET_MODE_IV_KEY
		    ^ sah_insert_skha_mode[sym_ctx->mode]
		    ^ sah_insert_skha_algorithm[key_info->algorithm];

		/* Honor 'no key parity checking' for DES and TDES */
		if ((key_info->flags & FSL_SKO_KEY_IGNORE_PARITY) &&
		    ((key_info->algorithm == FSL_KEY_ALG_DES) ||
		     (key_info->algorithm == FSL_KEY_ALG_TDES))) {
			header ^= sah_insert_skha_no_key_parity;
		}

		/* Header by default is decrypting, so... */
		if (encrypt == SYM_ENCRYPT) {
			header ^= sah_insert_skha_encrypt;
		}

		if (sym_ctx->mode == FSL_SYM_MODE_CTR) {
			header ^= sah_insert_skha_modulus[sym_ctx->modulus_exp];
		}

		if (sym_ctx->mode == FSL_SYM_MODE_ECB) {
			ptr1 = NULL;
			size1 = 0;
		} else if (sym_ctx->flags & FSL_SYM_CTX_INIT) {
			ptr1 = (uint8_t *) block_zeros;
		} else {
			ptr1 = sym_ctx->context;
		}

		DESC_IN_KEY(header, sym_ctx->block_size_bytes, ptr1, key_info);

		/* Add in-out data descriptor */
		if (length != 0) {
			header = SAH_HDR_SKHA_ENC_DEC;
			if (LENGTH_PATCH && (sym_ctx->mode == FSL_SYM_MODE_CTR)
			    && ((length & LENGTH_PATCH_MASK) != 0)) {
				sink = DESC_TEMP_ALLOC(LENGTH_PATCH);
				ret =
				    sah_Create_Link(user_ctx->mem_util, &link1,
						    (uint8_t *) in, length,
						    SAH_USES_LINK_DATA);
				ret =
				    sah_Append_Link(user_ctx->mem_util, link1,
						    (uint8_t *) sink,
						    LENGTH_PATCH -
						    (length &
						     LENGTH_PATCH_MASK),
						    SAH_USES_LINK_DATA);
				if (ret != FSL_RETURN_OK_S) {
					goto out;
				}
				ret =
				    sah_Create_Link(user_ctx->mem_util, &link2,
						    out, length,
						    SAH_USES_LINK_DATA |
						    SAH_OUTPUT_LINK);
				if (ret != FSL_RETURN_OK_S) {
					goto out;
				}
				ret = sah_Append_Link(user_ctx->mem_util, link2,
						      sink,
						      LENGTH_PATCH -
						      (length &
						       LENGTH_PATCH_MASK),
						      SAH_USES_LINK_DATA);
				if (ret != FSL_RETURN_OK_S) {
					goto out;
				}
				ret =
				    sah_Append_Desc(user_ctx->mem_util,
						    &desc_chain, header, link1,
						    link2);
				if (ret != FSL_RETURN_OK_S) {
					goto out;
				}
				link1 = link2 = NULL;
			} else {
				DESC_IN_OUT(header, length, in, length, out);
			}
		}

		/* Unload any desired context */
		if (sym_ctx->flags & FSL_SYM_CTX_SAVE) {
			DESC_OUT_OUT(SAH_HDR_SKHA_READ_CONTEXT_IV, 0, NULL,
				     sym_ctx->block_size_bytes,
				     sym_ctx->context);
		}

	}			/* not ARC4 */

	SAH_SF_EXECUTE();

      out:
	SAH_SF_DESC_CLEAN();
	DESC_TEMP_FREE(sink);
	if (LENGTH_PATCH) {
		sah_Destroy_Link(user_ctx->mem_util, link1);
		sah_Destroy_Link(user_ctx->mem_util, link2);
	}

	return ret;
}

/* REQ-S2LRD-PINTFC-API-BASIC-SYM-002 */
/* PINTFC-API-BASIC-SYM-ARC4-001 */
/* PINTFC-API-BASIC-SYM-ARC4-002 */

/*!
 * Compute symmetric encryption
 *
 *
 * @param    user_ctx
 * @param    key_info
 * @param    sym_ctx
 * @param    length
 * @param    pt
 * @param    ct
 *
 * @return    A return code of type #fsl_shw_return_t.
 */
fsl_shw_return_t fsl_shw_symmetric_encrypt(fsl_shw_uco_t * user_ctx,
					   fsl_shw_sko_t * key_info,
					   fsl_shw_scco_t * sym_ctx,
					   uint32_t length,
					   const uint8_t * pt, uint8_t * ct)
{
	fsl_shw_return_t ret;

	ret = do_symmetric(user_ctx, key_info, sym_ctx, SYM_ENCRYPT,
			   length, pt, ct);

	return ret;
}

/* PINTFC-API-BASIC-SYM-002 */
/* PINTFC-API-BASIC-SYM-ARC4-001 */
/* PINTFC-API-BASIC-SYM-ARC4-002 */

/*!
 * Compute symmetric decryption
 *
 *
 * @param    user_ctx
 * @param    key_info
 * @param    sym_ctx
 * @param    length
 * @param    pt
 * @param    ct
 *
 * @return    A return code of type #fsl_shw_return_t.
 */
fsl_shw_return_t fsl_shw_symmetric_decrypt(fsl_shw_uco_t * user_ctx,
					   fsl_shw_sko_t * key_info,
					   fsl_shw_scco_t * sym_ctx,
					   uint32_t length,
					   const uint8_t * ct, uint8_t * pt)
{
	fsl_shw_return_t ret;

	ret = do_symmetric(user_ctx, key_info, sym_ctx, SYM_DECRYPT,
			   length, ct, pt);

	return ret;
}
