/*
 * pnx8335_aes.c
 *
 * (C) Copyright 2008 Chris Steel, NXP Semiconductors
 * (C) Copyright 2007 Andre McCurdy, NXP Semiconductors
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/ioctl.h>
#include <linux/delay.h>
#include <linux/hardirq.h>
#include <linux/miscdevice.h>
#include <linux/scatterlist.h>
#include <linux/crypto.h>
#include <asm/byteorder.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#include <linux/pnx8335_aes.h>
#include <prjconfig.h>

/****************************************************************************/

struct aes_state
{
	unsigned int cfg;
};

typedef struct aes_state AES_KEY;

/*****************************************************************************
*****************************************************************************/

#ifndef CONFIG_HW_AES

static struct mutex aes_mutex;		/* misnomer... really the "aes_and_kc_mutex" */

#ifndef SGDX_REGS_BASE
#define SGDX_REGS_BASE				0xB7E1C000
#endif

#define REG_AES_CONFIG                  	0x430		/* AES config register */
  #define AES_CONFIG_SKEY_SELECT        	(1 << 9)
  #define AES_CONFIG_CTR_MODE           	(1 << 8)
  #define AES_CONFIG_DO_NOT_EXECUTE     	(1 << 7)
  #define AES_CONFIG_INIT_KEY           	(1 << 5)
  #define AES_CONFIG_PROCESS_ONE_WORD   	(2 << 5)
  #define AES_CONFIG_INIT_IV            	(3 << 5)
  #define AES_CONFIG_ECB                	(0 << 4)
  #define AES_CONFIG_CBC                	(1 << 4)
  #define AES_CONFIG_BYPASS             	(1 << 3)
  #define AES_CONFIG_DECRYPT            	(0 << 2)
  #define AES_CONFIG_ENCRYPT            	(1 << 2)
  #define AES_CONFIG_WORD_SWAP_A        	(0 << 0)
  #define AES_CONFIG_WORD_SWAP_B        	(1 << 0)
  #define AES_CONFIG_WORD_SWAP_C        	(2 << 0)
  #define AES_CONFIG_WORD_SWAP_D        	(3 << 0)
  #define AES_CONFIG_DEFAULT            	(0)

#define REG_AES_CMD                     	0x434		/* AES command register */
  #define AES_CMD_ABORT                 	(1 << 3)
  #define AES_CMD_SOFT_RESET            	(1 << 2)
  #define AES_CMD_PROCESS_BLOCK         	(1 << 1)
  #define AES_CMD_PROCESS_CONFIG        	(1 << 0)

#define REG_AES_BLOCK_COUNT             	0x438		/* 16Byte block count (max 1024) */

#define REG_AES_STATUS                  	0x43C		/* AES status */
  #define AES_STATUS_SKEY_VALID         	(1 << 16)
  #define AES_STATUS_STATE_MASK         	(0x07)
  #define AES_STATUS_STATE_SHIFT        	(13)
  #define AES_STATUS_READ_FREEZE        	(1 << 12)
  #define AES_STATUS_ZERO               	(1 << 11)
  #define AES_STATUS_CURRENT_WORD_MASK  	(0x7FF)
  #define AES_STATUS_CURRENT_WORD_SHIFT 	(0)

#define REG_AES_DATA_IN0                	0x440		/* AES data in [127:96] */
#define REG_AES_DATA_IN1                	0x444		/* AES data in [95:64] */
#define REG_AES_DATA_IN2                	0x448		/* AES data in [63:32] */
#define REG_AES_DATA_IN3                	0x44C		/* AES data in [31:0] */
#define REG_AES_DATA_OUT0               	0x450		/* AES data out [127:96] */
#define REG_AES_DATA_OUT1               	0x454		/* AES data out [95:64] */
#define REG_AES_DATA_OUT2               	0x458		/* AES data out [63:32] */
#define REG_AES_DATA_OUT3               	0x45C		/* AES data out [31:0] */

#define REG_AES_PKT_CFG                 	0x460		/* AES packet configuration */
  #define AES_PKT_CFG_HEADER_MASK       	(0xFF)
  #define AES_PKT_CFG_HEADER_SHIFT      	(16)
  #define AES_PKT_CFG_DATA_MASK         	(0xFFFF)
  #define AES_PKT_CFG_DATA_SHIFT        	(0)
  #define AES_PKT_CFG_DEFAULT           	(0x0000FFFF)

#define REG_AES_INCR_CTR                	0x464		/* AES value for counter increment */
#define REG_AES_CTR_PKT                 	0x468		/* AES packet size in CTR_MODE */

#endif

/*****************************************************************************
*****************************************************************************/

#ifndef KC_REGS_BASE
#define KC_REGS_BASE				0xB7E04000
#endif

#define REG_KC_UID_LSB				0x000		/* 31:0 : R- : UNIQUE_ID bits (31:0) */
#define REG_KC_UID_MSB				0x004		/* 31:0 : R- : UNIQUE_ID bits (63:32) */

#define REG_KC_STATUS				0x008		/* 15:0 : R- : Status register */

  #define KC_STATUS_FB2_KEY_VALID		(1 << 15)	/* Feedback key for VMSP path validity */
  #define KC_STATUS_OUT2_KEY_VALID		(1 << 14)	/* Output key for VMSP path validity */
  #define KC_STATUS_SESSION2_KEY_VALID		(1 << 13)	/* Session key for VMSP path validity */
  #define KC_STATUS_CHAINING2_KEY_VALID		(1 << 12)	/* Chaining key for VMSP path validity */
  #define KC_STATUS_PRIVATE2_KEY_VALID		(1 << 11)	/* Private key for VMSP path validity */
  #define KC_STATUS_OTP2_KEY_VALID		(1 << 10)	/* OTP secret key for VMSP path validity */
  #define KC_STATUS_FB1_KEY_VALID		(1 << 9)	/* Feedback key for SGdesc path validity */
  #define KC_STATUS_OUT1_KEY_VALID		(1 << 8)	/* Output key for SGDesc path validity */
  #define KC_STATUS_SESSION1_KEY_VALID		(1 << 7)	/* Session key for SGDesc path validity */
  #define KC_STATUS_CHAINING1_KEY_VALID		(1 << 6)	/* Chaining key for SGDesc path validity */
  #define KC_STATUS_PRIVATE1_KEY_VALID		(1 << 5)	/* Private key for SGDesc path validity */
  #define KC_STATUS_OTP1_KEY_VALID		(1 << 4)	/* OTP secret key for SGDesc path validity */
  #define KC_STATUS_KL_MODE_2_LEVEL		(0 << 2)	/* */
  #define KC_STATUS_KL_MODE_3_LEVEL		(1 << 2)	/* */
  #define KC_STATUS_KL_MODE_2_LEVEL_ALT 	(2 << 2)	/* */
  #define KC_STATUS_KL_MODE_RESERVED		(3 << 2)	/* */
  #define KC_STATUS_KL_MODE_MASK		(3 << 2)	/* */
  #define KC_STATUS_DISABLE_KEYS_OUT		(1 << 1)	/* Value of DISABLE_KEYS_OUT input. If 1, OTP secret key can not be output to SGDesc */
  #define KC_STATUS_DISABLE_CA_MODULE		(1 << 0)	/* Value of DISABLE_CA_MODULE input. If 1, the Keycontrol module is de-activated */

#define REG_KC_DATA1_LSB			0x00C		/* 31:0 : RW : Data1(31:0) input value */
#define REG_KC_DATA1_MSB			0x010		/* 31:0 : RW : Data1(63:32) input value */
#define REG_KC_CFG 				0x014		/*  7:0 : RW : Configuration register */

  #define KC_CFG_OUT_KEY_FROM_MEM		(1 << 7)	/* */
  #define KC_CFG_ALG_SELECT_ENC			(0 << 6)	/* */
  #define KC_CFG_ALG_SELECT_DEC			(1 << 6)	/* */
  #define KC_CFG_ALG_SELECT_MASK		(0x01 << 6)	/* */
  #define KC_CFG_RESULT_SEL_OUTPUT		(0 << 5)	/* */
  #define KC_CFG_RESULT_SEL_FB			(1 << 5)	/* */
  #define KC_CFG_RESULT_SEL_MASK		(0x01 << 5)	/* */
  #define KC_CFG_INPUT_SEL_DATAIN		(0 << 3)	/* */
  #define KC_CFG_INPUT_SEL_DATAIN_XOR_FB	(1 << 3)	/* */
  #define KC_CFG_INPUT_SEL_FB			(2 << 3)	/* */
  #define KC_CFG_INPUT_SEL_MASK			(0x03 << 3)	/* */
  #define KC_CFG_COMMAND_GEN_CHAIN1		(0 << 0)	/* */
  #define KC_CFG_COMMAND_GEN_SESSION1		(1 << 0)	/* */
  #define KC_CFG_COMMAND_GEN_CW1		(2 << 0)	/* */
  #define KC_CFG_COMMAND_GEN_CHAIN2		(4 << 0)	/* */
  #define KC_CFG_COMMAND_GEN_SESSION2		(5 << 0)	/* */
  #define KC_CFG_COMMAND_GEN_CW2		(6 << 0)	/* */
  #define KC_CFG_COMMAND_GEN_MASK		(0x07 << 0)	/* */

#define REG_KC_START				0x018		/*    0 : RW : Write 1 to cause start execution */
#define REG_KC_DATA2_LSB			0x020		/* 31:0 : RW : Data2(31:0) input value */
#define REG_KC_DATA2_MSB			0x024		/* 31:0 : RW : Data2(63:32) input value */
#define REG_KC_DATA3_LSB			0x028		/* 31:0 : RW : Data3(31:0) input value */
#define REG_KC_DATA3_MSB			0x02C		/* 31:0 : RW : Data3(63:32) input value */
#define REG_KC_INT_STATUS			0xFE0		/*  1:0 : R- : Interrupt status */
#define REG_KC_INT_ENABLE			0xFE4		/*  1:0 : RW : Interrupt enable */
#define REG_KC_INT_CLEAR			0xFE8		/*  1:0 : RW : Interrupt clear */
#define REG_KC_INT_SET				0xFEC		/*  1:0 : RW : Interrupt set */

  #define KC_INT_OK				(1 << 0)
  #define KC_INT_ERROR				(1 << 1)
  #define KC_INT_ALL				(0x03)

#define REG_KC_POWERDOWN			0xFF4		/*    0 : RW : To put the Keycontrol module in power down mode */
#define REG_KC_MODULE_ID			0xFFC		/* 31:0 : R- : Module Identification and revision information */


/*****************************************************************************
*****************************************************************************/

static unsigned int kc_reg_read (unsigned int reg_offset)
{
	return *((volatile unsigned int *) (KC_REGS_BASE + reg_offset));
}

static void kc_reg_write (unsigned int reg_offset, unsigned int value)
{
	*((volatile unsigned int *) (KC_REGS_BASE + reg_offset)) = value;
}


/*****************************************************************************
*****************************************************************************/

static unsigned int unaligned_read32_be (const unsigned char *src)
{
	unsigned int value;

	value = src[0];
	value = (value << 8) | src[1];
	value = (value << 8) | src[2];
	value = (value << 8) | src[3];

	return value;
}

static void kc_reg_write64 (unsigned int reg_offset, const unsigned char *src)
{
	kc_reg_write (reg_offset + 0, unaligned_read32_be (src + 4));
	kc_reg_write (reg_offset + 4, unaligned_read32_be (src + 0));
}

static void kc_data_write192 (const unsigned char *src)
{
	kc_reg_write64 (REG_KC_DATA1_LSB, src + 0);
	kc_reg_write64 (REG_KC_DATA2_LSB, src + 8);
	kc_reg_write64 (REG_KC_DATA3_LSB, src + 16);
}

/*****************************************************************************
*****************************************************************************/

static int kc_process (const unsigned char *data, unsigned int cmd)
{
	int intstat;

	kc_reg_write (REG_KC_INT_CLEAR, KC_INT_ALL);
	kc_reg_write (REG_KC_CFG, cmd);
	kc_data_write192 (data);
	kc_reg_write (REG_KC_START, 1);

	while ((intstat = kc_reg_read (REG_KC_INT_STATUS)) == 0)
		;

	kc_reg_write (REG_KC_INT_CLEAR, KC_INT_ALL);

	return (intstat == KC_INT_OK) ? 0 : -EINVAL;
}

static int kc_setup (const unsigned char *ck_ct, const unsigned char *sk_ct, const unsigned char *cw_ct)
{
	static const unsigned char keydata[24] = {
		0xC1,0xEE,0xE4,0xF2,0xE5,0xA0,0xCD,0xE3,
		0xC3,0xF5,0xF2,0xE4,0xF9,0xA0,0xB2,0xEB,
		0xB7,0xA0,0xC7,0xD0,0xCC,0xA0,0xA0,0x80
	};

	int result;
	int hw_bypass_disabled;
	int hw_levels_supported;
	unsigned int cmd1, cmd2, cmd3;
	unsigned int status = kc_reg_read (REG_KC_STATUS);

	if (status & KC_STATUS_DISABLE_CA_MODULE)
		return -EINVAL;

	if ((status & KC_STATUS_OTP1_KEY_VALID) != KC_STATUS_OTP1_KEY_VALID)
		return -EINVAL;

	switch (status & KC_STATUS_KL_MODE_MASK)
	{
		case KC_STATUS_KL_MODE_2_LEVEL:
		case KC_STATUS_KL_MODE_2_LEVEL_ALT:
			hw_levels_supported = 2;
			break;

		case KC_STATUS_KL_MODE_3_LEVEL:
			hw_levels_supported = 3;
			break;

		default:
			return -EINVAL;
	}

	hw_bypass_disabled = (status & KC_STATUS_DISABLE_KEYS_OUT) ? 1 : 0;

	cmd1 = (KC_CFG_ALG_SELECT_DEC | KC_CFG_COMMAND_GEN_CHAIN1);
	cmd2 = (KC_CFG_ALG_SELECT_DEC | KC_CFG_COMMAND_GEN_SESSION1);
	cmd3 = (KC_CFG_ALG_SELECT_DEC | KC_CFG_COMMAND_GEN_CW1);

	if ((ck_ct == NULL) && (sk_ct == NULL) && (cw_ct == NULL)) {
		if (hw_bypass_disabled)
			return -EINVAL;
		ck_ct = keydata;
		sk_ct = keydata;
		cw_ct = keydata;
	}
	else {
		if (((ck_ct == NULL) && (hw_levels_supported != 2)) ||
		    ((ck_ct != NULL) && (hw_levels_supported != 3)))
			return -EINVAL;
		cmd3 |= KC_CFG_OUT_KEY_FROM_MEM;
	}

	if (hw_levels_supported == 3) {
		if ((result = kc_process (ck_ct, cmd1)) != 0)
			return result;
	}

	if ((result = kc_process (sk_ct, cmd2)) != 0)
		return result;

	return kc_process (cw_ct, cmd3);
}

#ifndef CONFIG_HW_AES

static void unaligned_write32_be (unsigned char *dst, unsigned int value)
{
	dst[0] = (unsigned char) (value >> 24);
	dst[1] = (unsigned char) (value >> 16);
	dst[2] = (unsigned char) (value >> 8);
	dst[3] = (unsigned char) (value >> 0);
}

static unsigned int aes_reg_read (unsigned int reg_offset)
{
	return *((volatile unsigned int *) (SGDX_REGS_BASE + reg_offset));
}

static void aes_reg_write (unsigned int reg_offset, unsigned int value)
{
	*((volatile unsigned int *) (SGDX_REGS_BASE + reg_offset)) = value;
}

static void aes_datain_write128 (const unsigned char *src)
{
	aes_reg_write (REG_AES_DATA_IN0, unaligned_read32_be (src + 0));
	aes_reg_write (REG_AES_DATA_IN1, unaligned_read32_be (src + 4));
	aes_reg_write (REG_AES_DATA_IN2, unaligned_read32_be (src + 8));
	aes_reg_write (REG_AES_DATA_IN3, unaligned_read32_be (src + 12));
}

static void aes_dataout_read128 (unsigned char *dst)
{
	unaligned_write32_be (dst + 0, aes_reg_read (REG_AES_DATA_OUT0));
	unaligned_write32_be (dst + 4, aes_reg_read (REG_AES_DATA_OUT1));
	unaligned_write32_be (dst + 8, aes_reg_read (REG_AES_DATA_OUT2));
	unaligned_write32_be (dst + 12, aes_reg_read (REG_AES_DATA_OUT3));
}

static int aes_issue_cmd (unsigned int cmd)
{
	int i;

	aes_reg_write (REG_AES_CMD, cmd);

	for (i = 0; i < 10; i++) {
		unsigned int state = aes_reg_read (REG_AES_CMD);
		if (state == 0)
			break;
		printk ("%s: waiting for cmd to complete... 0x%08x\n", __FUNCTION__, state);
		udelay (10);
	}

	return 0;
}

static void aes_hw_init (void)
{
	aes_issue_cmd (AES_CMD_ABORT);		/* is this necessary ?? */
	aes_issue_cmd (AES_CMD_SOFT_RESET);	/* reset internal state (but not externally visible registers !?!) */

	aes_reg_write (REG_AES_PKT_CFG, AES_PKT_CFG_DEFAULT);
	aes_reg_write (REG_AES_BLOCK_COUNT, 0);
}

static int aes_set_key (const unsigned char *keydata, AES_KEY *key, int encdec, int skey)
{
	key->cfg = (encdec == DECRYPT) ? AES_CONFIG_DECRYPT : AES_CONFIG_ENCRYPT;

	aes_hw_init();

	if (skey) {
		if ((aes_reg_read (REG_AES_STATUS) & AES_STATUS_SKEY_VALID) == 0)
			return -EINVAL;
		key->cfg |= AES_CONFIG_SKEY_SELECT;
	}
	else {
		aes_datain_write128 (keydata);
	}

	aes_reg_write (REG_AES_CONFIG, (key->cfg | AES_CONFIG_INIT_KEY));

	return aes_issue_cmd (AES_CMD_PROCESS_CONFIG);
}

static void aes_process_block (const unsigned char *in, unsigned char *out, const AES_KEY *key)
{
	aes_datain_write128 (in);
	aes_reg_write (REG_AES_CONFIG, (key->cfg | AES_CONFIG_PROCESS_ONE_WORD));
	aes_issue_cmd (AES_CMD_PROCESS_CONFIG);
	aes_dataout_read128 (out);
}

static int AES_set_decrypt_key_kc (const unsigned char *ck_ct, const unsigned char *sk_ct, const unsigned char *cw_ct, AES_KEY *key)
{
	int result;

	if ((result = kc_setup (ck_ct, sk_ct, cw_ct)) != 0)
		return result;

	return aes_set_key (NULL, key, DECRYPT, 1);
}

static int AES_set_encrypt_key_kc (const unsigned char *ck_ct, const unsigned char *sk_ct, const unsigned char *cw_ct, AES_KEY *key)
{
	int result;

	if ((result = kc_setup (ck_ct, sk_ct, cw_ct)) != 0)
		return result;

	return aes_set_key (NULL, key, ENCRYPT, 1);
}

static int AES_set_decrypt_key (const unsigned char *keydata, const int bits, AES_KEY *key)
{
	return aes_set_key (keydata, key, DECRYPT, 0);
}

static int AES_set_encrypt_key (const unsigned char *keydata, const int bits, AES_KEY *key)
{
	return aes_set_key (keydata, key, ENCRYPT, 0);
}

static void AES_decrypt (const unsigned char *in, unsigned char *out, const AES_KEY *key)
{
	aes_process_block (in, out, key);
}

static void AES_encrypt (const unsigned char *in, unsigned char *out, const AES_KEY *key)
{
	aes_process_block (in, out, key);
}

static void abn_inc_128be (unsigned char *bn)
{
	unsigned char t1, t2, c1, c2 = 0, inc = 1;
	unsigned char *bnp = bn + 15;

	while (1) {
		t1 = *bnp;
		t2 = t1 + inc;
		inc = 0;
		c1 = (t2 < t1) ? 1 : 0;
		t1 = t2 + c2;
		c2 = (t1 < t2) ? 1 : c1;
		*bnp = t1;
		if (bnp == bn)
			break;
		bnp--;
	}
}

static int aes_process_op (struct aes_op *op, int in_kernel)
{
	AES_KEY key;
	unsigned int iv[4] = { 0 };
	unsigned int buf_in[4];
	unsigned int buf_out[4];
	unsigned char *keydata = NULL;
	unsigned char *ck_ct = NULL;
	unsigned char *sk_ct = NULL;
	unsigned char *cw_ct = NULL;
	unsigned int blocks;
	unsigned char *src;
	unsigned char *dst;
	int encdec, mode, keyselect;
	int result;

	encdec = AESCMD_EXTRACT_ED (op->cmd);

	keyselect = AESCMD_EXTRACT_KEY (op->cmd);

	switch (keyselect) {
		case KEY_USER:
			keydata = op->key;
			break;

		case KEY_KC_BYPASS:
			break;

		case KEY_KC_2_LEVEL:
			sk_ct = op->sk_ct;
			cw_ct = op->cw_ct;
			break;

		case KEY_KC_3_LEVEL:
			ck_ct = op->ck_ct;
			sk_ct = op->sk_ct;
			cw_ct = op->cw_ct;
			break;

		default:
			return -EINVAL;
	}

#if 0
	printk ("%s: keyselect: %s\n", __FUNCTION__,
		(keyselect == KEY_USER) ? "user" :
		(keyselect == KEY_KC_BYPASS) ? "kc_bypass" :
		(keyselect == KEY_KC_2_LEVEL) ? "kc_2_level" :
		(keyselect == KEY_KC_3_LEVEL) ? "kc_3_level" : "unknown");
#endif

	mode = AESCMD_EXTRACT_MODE (op->cmd);

	switch (mode) {
		case MODE_ECB:
			break;

		case MODE_CBC:
			memcpy (iv, op->iv, sizeof(iv));
			break;

#if defined (ENABLE_CTR_MODE)
		case MODE_CTR:
			memcpy (iv, op->iv, sizeof(iv));
			encdec = ENCRYPT;
			break;
#endif

		default:
			return -EINVAL;
	}

	src = op->src;
	dst = op->dst;
	blocks = op->blocks;

	if (blocks == 0)
		return 0;

	mutex_lock (&aes_mutex);

	if (keyselect == KEY_USER) {
		if (encdec == DECRYPT)
			result = AES_set_decrypt_key (keydata, 128, &key);
		else
			result = AES_set_encrypt_key (keydata, 128, &key);
	}
	else {
		if (encdec == DECRYPT)
			result = AES_set_decrypt_key_kc (ck_ct, sk_ct, cw_ct, &key);
		else
			result = AES_set_encrypt_key_kc (ck_ct, sk_ct, cw_ct, &key);
	}

	if (result != 0) {
		mutex_unlock (&aes_mutex);
		return result;
	}

	while (blocks--) {
		if (in_kernel)
			memcpy (buf_in, src, sizeof(buf_in));
		else {
			if (copy_from_user ((unsigned char *) buf_in, src, sizeof(buf_in)) != 0) {
				result = -EFAULT;
				break;
			}
		}

		if (encdec == DECRYPT) {
			AES_decrypt ((unsigned char *) buf_in, (unsigned char *) buf_out, &key);
			if (mode == MODE_CBC) {
				buf_out[0] ^= iv[0];
				buf_out[1] ^= iv[1];
				buf_out[2] ^= iv[2];
				buf_out[3] ^= iv[3];
				iv[0] = buf_in[0];
				iv[1] = buf_in[1];
				iv[2] = buf_in[2];
				iv[3] = buf_in[3];
			}
		}
		else {
			if (mode == MODE_CTR) {
				AES_encrypt ((unsigned char *) iv, (unsigned char *) buf_out, &key);
				buf_out[0] ^= buf_in[0];
				buf_out[1] ^= buf_in[1];
				buf_out[2] ^= buf_in[2];
				buf_out[3] ^= buf_in[3];
				abn_inc_128be ((unsigned char *) iv);
			}
			else
			{
				if (mode == MODE_CBC) {
					buf_in[0] ^= iv[0];
					buf_in[1] ^= iv[1];
					buf_in[2] ^= iv[2];
					buf_in[3] ^= iv[3];
				}
				AES_encrypt ((unsigned char *) buf_in, (unsigned char *) buf_out, &key);
				if (mode == MODE_CBC) {
					iv[0] = buf_out[0];
					iv[1] = buf_out[1];
					iv[2] = buf_out[2];
					iv[3] = buf_out[3];
				}
			}
		}

		if (in_kernel)
			memcpy (dst, buf_out, sizeof(buf_out));
		else {
			if (copy_to_user (dst, (unsigned char *) buf_out, sizeof(buf_out)) != 0) {
				result = -EFAULT;
				break;
			}
		}

		src += 16;
		dst += 16;
	}

	mutex_unlock (&aes_mutex);

#if defined (ENABLE_CTR_MODE)
	if ((mode == MODE_CBC) || (mode == MODE_CTR)) {
#else
	if (mode == MODE_CBC) {
#endif
		memcpy (op->iv, iv, sizeof(iv));
	}

	return result;
}
#else
static int aes_process_op (struct aes_op *op, int in_kernel)
{
	struct crypto_blkcipher *tfm;
	struct scatterlist sg_src[1];
	struct scatterlist sg_dst[1];
	struct blkcipher_desc desc;
	unsigned char *buf_in;
	unsigned char *buf_out;
	unsigned int bytesToProcess;
	int encdec, mode, keyselect;
	int result = 0;

	encdec         = AESCMD_EXTRACT_ED (op->cmd);
	keyselect      = AESCMD_EXTRACT_KEY (op->cmd);
	mode           = AESCMD_EXTRACT_MODE (op->cmd);
	bytesToProcess = op->blocks * 16;

	if (bytesToProcess == 0)
	{
		return 0;
	}

	switch (mode)
	{
		case MODE_ECB:
			tfm = crypto_alloc_blkcipher("ecb(aes)", 0, CRYPTO_ALG_ASYNC);
			break;

		case MODE_CBC:
			tfm = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC);
			crypto_blkcipher_set_iv(tfm, op->iv, 16);
			break;

		case MODE_CTR:
			tfm = crypto_alloc_blkcipher("ctr(aes)", 0, CRYPTO_ALG_ASYNC);
			crypto_blkcipher_set_iv(tfm, op->iv, 16);
			break;

		default:
			tfm = NULL;
	}

	if (tfm)
	{
		buf_in = kmalloc(PAGE_SIZE, GFP_KERNEL);

		if (buf_in)
		{
			int bytesProcessed = 0;

			buf_out    = buf_in;
			desc.tfm   = tfm;
			desc.flags = 0;
			sg_init_one(sg_src, buf_in,  PAGE_SIZE);
			sg_init_one(sg_dst, buf_out, PAGE_SIZE);
			
			if (keyselect == KEY_USER)
			{
				result = crypto_blkcipher_setkey(tfm, op->key, 16);
			}
			else
			{
				desc.flags |= 1;
				switch (AESCMD_EXTRACT_KEY (op->cmd))
				{
					case KEY_KC_BYPASS:
						result = kc_setup (NULL, NULL, NULL);
						break;
		
					case KEY_KC_2_LEVEL:
						result = kc_setup (NULL, op->sk_ct, op->cw_ct);
						break;
		
					case KEY_KC_3_LEVEL:
						result = kc_setup (op->ck_ct, op->sk_ct, op->cw_ct);
						break;
	
					default:
						result = -EINVAL;
				}
			}

			do
			{
				int bytesInBlock = (bytesToProcess > PAGE_SIZE) ? PAGE_SIZE : bytesToProcess;

				if (!result)
				{
					if (in_kernel)
					{
						memcpy (buf_in, op->src + bytesProcessed, bytesInBlock);
					}
					else
					{
						if (copy_from_user (buf_in, op->src + bytesProcessed, bytesInBlock) != 0)
						{
							result = -EFAULT;
						}
					}
				}

				if (!result)
				{
					if (encdec == DECRYPT)
					{
						result = crypto_blkcipher_decrypt(&desc, sg_src, sg_dst, bytesInBlock);
					}
					else
					{
						result = crypto_blkcipher_encrypt(&desc, sg_src, sg_dst, bytesInBlock);
					}
	
					if (!result)
					{
						if ((mode == MODE_CBC) || (mode == MODE_CTR))
						{
							crypto_blkcipher_get_iv(tfm, op->iv, 16);
						}

						if (in_kernel)
						{
							memcpy(op->dst + bytesProcessed, buf_out, bytesInBlock);
						}
						else
						{
							if (copy_to_user (op->dst + bytesProcessed, buf_out, bytesInBlock) != 0)
							{
								result = -EFAULT;
							}
						}
					}
				}
				bytesToProcess -= bytesInBlock;
				bytesProcessed += bytesInBlock;
			} while (bytesToProcess);

			kfree(buf_in);
		}
		else
		{
			result = -EFAULT;
		}

		crypto_free_blkcipher(tfm);
	}
	else
	{
		result = -EINVAL;
	}

	return result;
}
#endif

int aes_process_op_in_kernel (struct aes_op *op)
{
	if (in_interrupt())
		return -1;

	return aes_process_op (op, 1);
}

static int aes_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct aes_op op;

	if (cmd != IOCTL_NXPAES_OP)
		return -EINVAL;

	if (copy_from_user (&op, (void *) arg, sizeof(op)) != 0)
		return -EFAULT;

	if (aes_process_op (&op, 0) != 0)
		return -EINVAL;

	if (copy_to_user ((void *) arg, &op, sizeof(op)) != 0)
		return -EFAULT;

	return 0;
}

static struct file_operations aes_fops = {
	.owner = THIS_MODULE,
	.ioctl = aes_ioctl,
};

struct miscdevice aesdev = {
	.minor = NXPAES_MINOR,
	.name = "aes",
	.fops = &aes_fops,
};

static int __init aes_init (void)
{
	int result;

#ifndef CONFIG_HW_AES
	mutex_init (&aes_mutex);
#endif
	result = misc_register (&aesdev);
	printk ("PNX8335 AES: %s\n", result ? "loading failed !!" : "loaded");
	return result;
}

static void __exit aes_exit (void)
{
	misc_deregister (&aesdev);
	printk ("PNX8335 AES: unloaded\n");
}

module_init(aes_init);
module_exit(aes_exit);

EXPORT_SYMBOL(aes_process_op_in_kernel);

MODULE_AUTHOR("Andre McCurdy, NXP Semiconductors");
MODULE_LICENSE("GPL");

