From 788c4752ced473d4f9b5fe9acbd139c7a6817a84 Mon Sep 17 00:00:00 2001
From: Nikos Mavrogiannopoulos <nmav@gnutls.org>
Date: Wed, 23 Nov 2011 20:35:33 +0100
Subject: [PATCH 146/291] cryptodev: Added

---
 drivers/staging/crypto/cryptodev/.gitignore        |   2 +
 drivers/staging/crypto/cryptodev/Makefile          |   2 +-
 .../staging/crypto/cryptodev/crypto/cryptodev.h    |  28 +-
 drivers/staging/crypto/cryptodev/cryptodev_auth.c  | 458 +++++++++++++++++++++
 drivers/staging/crypto/cryptodev/cryptodev_int.h   |  54 +++
 drivers/staging/crypto/cryptodev/cryptodev_main.c  | 114 ++---
 drivers/staging/crypto/cryptodev/examples/Makefile |   3 +-
 .../crypto/cryptodev/examples/cipher-aead.c        | 271 ++++++++++++
 8 files changed, 873 insertions(+), 59 deletions(-)
 create mode 100644 drivers/staging/crypto/cryptodev/cryptodev_auth.c
 create mode 100644 drivers/staging/crypto/cryptodev/examples/cipher-aead.c

diff --git a/drivers/staging/crypto/cryptodev/.gitignore b/drivers/staging/crypto/cryptodev/.gitignore
index 356d53acaaec..247b56216add 100644
--- a/drivers/staging/crypto/cryptodev/.gitignore
+++ b/drivers/staging/crypto/cryptodev/.gitignore
@@ -18,3 +18,5 @@ examples/hashcrypt_speed
 releases
 scripts
 version.h
+examples/cipher-aead
+examples/fullspeed
diff --git a/drivers/staging/crypto/cryptodev/Makefile b/drivers/staging/crypto/cryptodev/Makefile
index d12c08bdd3b0..a90cd8bda24d 100644
--- a/drivers/staging/crypto/cryptodev/Makefile
+++ b/drivers/staging/crypto/cryptodev/Makefile
@@ -2,7 +2,7 @@ KBUILD_CFLAGS += -I$(src)
 KERNEL_DIR = /lib/modules/$(shell uname -r)/build
 VERSION = 1.0
 
-cryptodev-objs = cryptodev_main.o cryptodev_cipher.o
+cryptodev-objs = cryptodev_main.o cryptodev_cipher.o cryptodev_auth.o
 
 obj-m += cryptodev.o
 
diff --git a/drivers/staging/crypto/cryptodev/crypto/cryptodev.h b/drivers/staging/crypto/cryptodev/crypto/cryptodev.h
index 6b0205ba2f15..0610282d16d8 100644
--- a/drivers/staging/crypto/cryptodev/crypto/cryptodev.h
+++ b/drivers/staging/crypto/cryptodev/crypto/cryptodev.h
@@ -57,7 +57,7 @@ enum cryptodev_crypto_op_t {
 #define DES3_BLOCK_LEN		8
 #define RIJNDAEL128_BLOCK_LEN	16
 #define AES_BLOCK_LEN		RIJNDAEL128_BLOCK_LEN
-#define CAMELLIA_BLOCK_LEN
+#define CAMELLIA_BLOCK_LEN      16
 #define BLOWFISH_BLOCK_LEN	8
 #define SKIPJACK_BLOCK_LEN	8
 #define CAST128_BLOCK_LEN	8
@@ -104,7 +104,7 @@ struct session_info_op {
 #define COP_DECRYPT	1
 
 /* input of CIOCCRYPT */
- struct crypt_op {
+struct crypt_op {
 	__u32	ses;		/* session identifier */
 	__u16	op;		/* COP_ENCRYPT or COP_DECRYPT */
 	__u16	flags;		/* see COP_FLAG_* */
@@ -117,6 +117,24 @@ struct session_info_op {
 	__u8	__user *iv;
 };
 
+/* input of CIOCAUTHCRYPT */
+struct crypt_auth_op {
+	__u32	ses;		/* session identifier */
+	__u16	op;		/* COP_ENCRYPT or COP_DECRYPT */
+	__u16	flags;		/* see COP_FLAG_* */
+	__u32	len;		/* length of source data */
+	__u32	auth_len;		/* length of auth data */
+	__u32	tag_len;		/* the length of the tag */
+	__u8	__user *auth_src;	/* authenticated-only data */
+	__u8	__user *src;	/* data to be encrypted and authenticated */
+	__u8	__user *dst;	/* pointer to output data. Must have
+	                         * space for tag. This should be at least 
+	                         * src_len + tag_size + block_size for padding */
+
+	/* initialization vector for encryption operations */
+	__u8	__user *iv;
+};
+
 /* struct crypt_op flags */
 
 #define COP_FLAG_NONE		(0 << 0) /* totally no flag */
@@ -124,6 +142,9 @@ struct session_info_op {
 #define COP_FLAG_FINAL		(1 << 1) /* multi-update final hash mode */
 #define COP_FLAG_WRITE_IV	(1 << 2) /* update the IV during operation */
 #define COP_FLAG_NO_ZC		(1 << 3) /* do not zero-copy */
+#define COP_FLAG_AEAD_TLS_TYPE  (1 << 4) /* authenticate and encrypt using the 
+                                          * protocol TLS rules */
+
 
 /* Stuff for bignum arithmetic and public key
  * cryptography - not supported yet by linux
@@ -189,4 +210,7 @@ enum cryptodev_crk_op_t {
 #define CIOCASYNCCRYPT    _IOW('c', 107, struct crypt_op)
 #define CIOCASYNCFETCH    _IOR('c', 108, struct crypt_op)
 
+/* additional ioctls for AEAD */
+#define CIOCAUTHCRYPT   _IOWR('c', 109, struct crypt_auth_op)
+
 #endif /* L_CRYPTODEV_H */
diff --git a/drivers/staging/crypto/cryptodev/cryptodev_auth.c b/drivers/staging/crypto/cryptodev/cryptodev_auth.c
new file mode 100644
index 000000000000..546f295fdf56
--- /dev/null
+++ b/drivers/staging/crypto/cryptodev/cryptodev_auth.c
@@ -0,0 +1,458 @@
+/*
+ * Driver for /dev/crypto device (aka CryptoDev)
+ *
+ * Copyright (c) 2004 Michal Ludvig <mludvig@logix.net.nz>, SuSE Labs
+ * Copyright (c) 2009,2010 Nikos Mavrogiannopoulos <nmav@gnutls.org>
+ *
+ * This file is part of linux cryptodev.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/*
+ * Device /dev/crypto provides an interface for
+ * accessing kernel CryptoAPI algorithms (ciphers,
+ * hashes) from userspace programs.
+ *
+ * /dev/crypto interface was originally introduced in
+ * OpenBSD and this module attempts to keep the API.
+ *
+ */
+
+#include <crypto/hash.h>
+#include <linux/crypto.h>
+#include <linux/mm.h>
+#include <linux/highmem.h>
+#include <linux/ioctl.h>
+#include <linux/random.h>
+#include <linux/syscalls.h>
+#include <linux/pagemap.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+#include <crypto/cryptodev.h>
+#include <crypto/scatterwalk.h>
+#include <linux/scatterlist.h>
+#include "cryptodev_int.h"
+#include "version.h"
+
+
+/* Idea zero-copy src data 
+ * Read source data and encrypt to temporary buffer
+ * then copy temporary buffer to dst.
+ */
+
+/* make cop->src and cop->dst available in scatterlists */
+static int get_userbuf3(struct csession *ses, struct kernel_crypt_auth_op *kcaop,
+			struct scatterlist **auth_sg, struct scatterlist **dst_sg, 
+			int *tot_pages)
+{
+	int dst_pagecount = 0, pagecount;
+	int auth_pagecount = 0;
+	struct crypt_auth_op *caop = &kcaop->caop;
+	int rc;
+
+	if (caop->dst == NULL && caop->auth_src == NULL)
+		return -EINVAL;
+
+	if (ses->alignmask) {
+		if (!IS_ALIGNED((unsigned long)caop->dst, ses->alignmask))
+			dprintk(2, KERN_WARNING, "%s: careful - source address %lx is not %d byte aligned\n",
+				__func__, (unsigned long)caop->dst, ses->alignmask + 1);
+		if (!IS_ALIGNED((unsigned long)caop->auth_src, ses->alignmask))
+			dprintk(2, KERN_WARNING, "%s: careful - source address %lx is not %d byte aligned\n",
+				__func__, (unsigned long)caop->auth_src, ses->alignmask + 1);
+	}
+
+	if (kcaop->dst_len == 0) {
+		dprintk(1, KERN_WARNING, "Destination cannot be zero\n");
+		return -EINVAL;
+	}
+
+	if (caop->auth_len != 0)
+		auth_pagecount = PAGECOUNT(caop->auth_src, caop->auth_len);
+
+	dst_pagecount = PAGECOUNT(caop->dst, kcaop->dst_len);
+
+	(*tot_pages) = pagecount = auth_pagecount + dst_pagecount;
+
+	rc = adjust_sg_array(ses, pagecount);
+	if (rc)
+		return rc;
+
+	if (auth_pagecount > 0) {
+		rc = __get_userbuf(caop->auth_src, caop->auth_len, 0, auth_pagecount,
+				   ses->pages, ses->sg, kcaop->task, kcaop->mm);
+		if (unlikely(rc)) {
+			dprintk(1, KERN_ERR,
+				"failed to get user pages for data input\n");
+			return -EINVAL;
+		}
+		(*auth_sg) = ses->sg;
+	} else {
+		(*auth_sg) = NULL;
+	}
+
+	rc = __get_userbuf(caop->dst, kcaop->dst_len, 1, dst_pagecount,
+	                   ses->pages + auth_pagecount, ses->sg, kcaop->task, kcaop->mm);
+	if (unlikely(rc)) {
+		release_user_pages(ses->pages, auth_pagecount);
+		dprintk(1, KERN_ERR,
+			"failed to get user pages for data input\n");
+		return -EINVAL;
+	}
+	
+	(*dst_sg) = ses->sg + auth_pagecount;
+
+	return 0;
+}
+
+
+static int fill_kcaop_from_caop(struct kernel_crypt_auth_op *kcaop, struct fcrypt *fcr)
+{
+	struct crypt_auth_op *caop = &kcaop->caop;
+	struct csession *ses_ptr;
+	int rc;
+
+	/* this also enters ses_ptr->sem */
+	ses_ptr = crypto_get_session_by_sid(fcr, caop->ses);
+	if (unlikely(!ses_ptr)) {
+		dprintk(1, KERN_ERR, "invalid session ID=0x%08X\n", caop->ses);
+		return -EINVAL;
+	}
+
+	if (caop->src != caop->dst) {
+		dprintk(1, KERN_ERR,
+			"Only inplace encryption and decryption is supported\n");
+		rc = -EINVAL;
+		goto out_unlock;
+	}
+
+	if (caop->tag_len == 0)
+		caop->tag_len = ses_ptr->hdata.digestsize;
+
+	kcaop->ivlen = caop->iv ? ses_ptr->cdata.ivsize : 0;
+	kcaop->dst_len = caop->len + ses_ptr->cdata.blocksize /* pad */ + caop->tag_len;
+
+	kcaop->task = current;
+	kcaop->mm = current->mm;
+
+	if (caop->iv) {
+		rc = copy_from_user(kcaop->iv, caop->iv, kcaop->ivlen);
+		if (unlikely(rc)) {
+			dprintk(1, KERN_ERR,
+				"error copying IV (%d bytes), copy_from_user returned %d for address %lx\n",
+				kcaop->ivlen, rc, (unsigned long)caop->iv);
+			rc = -EFAULT;
+			goto out_unlock;
+		}
+	}
+	
+	rc = 0;
+
+out_unlock:
+	crypto_put_session(ses_ptr);
+	return rc;
+
+}
+
+static int fill_caop_from_kcaop(struct kernel_crypt_auth_op *kcaop, struct fcrypt *fcr)
+{
+	int ret;
+
+	kcaop->caop.len = kcaop->dst_len;
+
+	if (kcaop->ivlen && kcaop->caop.flags & COP_FLAG_WRITE_IV) {
+		ret = copy_to_user(kcaop->caop.iv,
+				kcaop->iv, kcaop->ivlen);
+		if (unlikely(ret)) {
+			dprintk(1, KERN_ERR, "Error in copying to userspace\n");
+			return -EFAULT;
+		}
+	}
+	return 0;
+}
+
+
+int kcaop_from_user(struct kernel_crypt_auth_op *kcaop,
+			struct fcrypt *fcr, void __user *arg)
+{
+	if (unlikely(copy_from_user(&kcaop->caop, arg, sizeof(kcaop->caop)))) {
+		dprintk(1, KERN_ERR, "Error in copying from userspace\n");
+		return -EFAULT;
+	}
+
+	return fill_kcaop_from_caop(kcaop, fcr);
+}
+
+int kcaop_to_user(struct kernel_crypt_auth_op *kcaop,
+		struct fcrypt *fcr, void __user *arg)
+{
+	int ret;
+
+	ret = fill_caop_from_kcaop(kcaop, fcr);
+	if (unlikely(ret)) {
+		dprintk(1, KERN_ERR, "fill_caop_from_kcaop\n");
+		return ret;
+	}
+
+	if (unlikely(copy_to_user(arg, &kcaop->caop, sizeof(kcaop->caop)))) {
+		dprintk(1, KERN_ERR, "Error in copying to userspace\n");
+		return -EFAULT;
+	}
+	return 0;
+}
+
+static void copy_hash( struct scatterlist *dst_sg, int len, void* hash, int hash_len)
+{
+	scatterwalk_map_and_copy(hash, dst_sg, len, hash_len, 1);
+}
+
+static void read_hash( struct scatterlist *dst_sg, int len, void* hash, int hash_len)
+{
+	scatterwalk_map_and_copy(hash, dst_sg, len-hash_len, hash_len, 0);
+}
+
+static int pad_record( struct scatterlist *dst_sg, int len, int block_size)
+{
+	uint8_t pad[block_size];
+	int pad_size = block_size - (len % block_size);
+
+	memset(pad, pad_size-1, pad_size);
+
+	scatterwalk_map_and_copy(pad, dst_sg, len, pad_size, 1);
+
+	return pad_size;
+}
+
+static int verify_record_pad( struct scatterlist *dst_sg, int len, int block_size)
+{
+	uint8_t pad[256]; /* the maximum allowed */
+	uint8_t pad_size;
+	int i;
+
+	scatterwalk_map_and_copy(&pad_size, dst_sg, len-1, 1, 0);
+	pad_size++;
+
+	if (pad_size > len)
+		return -ECANCELED;
+
+	scatterwalk_map_and_copy(pad, dst_sg, len-pad_size, pad_size, 0);
+
+	for (i=0;i<pad_size;i++)
+		if (pad[i] != pad_size)
+			return -ECANCELED;
+
+	return 0;
+}
+
+static int
+tls_auth_n_crypt(struct csession *ses_ptr, struct kernel_crypt_auth_op *kcaop,
+		struct scatterlist *auth_sg, uint32_t auth_len,
+		struct scatterlist *dst_sg, uint32_t len)
+{
+	int ret, fail = 0;
+	struct crypt_auth_op *caop = &kcaop->caop;
+	uint8_t vhash[AALG_MAX_RESULT_LEN];
+	uint8_t hash_output[AALG_MAX_RESULT_LEN];
+
+	/* Always hash before encryption and after decryption. Maybe
+	 * we should introduce a flag to switch... TBD later on.
+	 */
+	if (caop->op == COP_ENCRYPT) {
+		if (ses_ptr->hdata.init != 0) {
+			if (auth_len > 0) {
+				ret = cryptodev_hash_update(&ses_ptr->hdata,
+								auth_sg, auth_len);
+				if (unlikely(ret)) {
+					dprintk(0, KERN_ERR, "cryptodev_hash_update: %d\n", ret);
+					goto out_err;
+				}
+			}
+
+			if (len > 0) {
+				ret = cryptodev_hash_update(&ses_ptr->hdata,
+								dst_sg, len);
+				if (unlikely(ret)) {
+					dprintk(0, KERN_ERR, "cryptodev_hash_update: %d\n", ret);
+					goto out_err;
+				}
+			}
+
+			ret = cryptodev_hash_final(&ses_ptr->hdata, hash_output);
+			if (unlikely(ret)) {
+				dprintk(0, KERN_ERR, "cryptodev_hash_final: %d\n", ret);
+				goto out_err;
+			}
+
+			copy_hash( dst_sg, len, hash_output, caop->tag_len);
+			len += caop->tag_len;
+		}
+
+		if (ses_ptr->cdata.init != 0) {
+			if (ses_ptr->cdata.blocksize > 1) {
+				ret = pad_record(dst_sg, len, ses_ptr->cdata.blocksize);
+				len += ret;
+			}
+
+			ret = cryptodev_cipher_encrypt(&ses_ptr->cdata,
+							dst_sg, dst_sg, len);
+			if (unlikely(ret)) {
+				dprintk(0, KERN_ERR, "cryptodev_cipher_encrypt: %d\n", ret);
+				goto out_err;
+			}
+		}
+	} else {
+		if (ses_ptr->cdata.init != 0) {
+			ret = cryptodev_cipher_decrypt(&ses_ptr->cdata,
+							dst_sg, dst_sg, len);
+
+			if (unlikely(ret)) {
+				dprintk(0, KERN_ERR, "cryptodev_cipher_decrypt: %d\n", ret);
+				goto out_err;
+			}
+
+			if (ses_ptr->cdata.blocksize > 1) {
+				ret = verify_record_pad(dst_sg, len, ses_ptr->cdata.blocksize);
+				if (unlikely(ret)) {
+					dprintk(0, KERN_ERR, "verify_record_pad: %d\n", ret);
+					fail = 1;
+				} else
+					len -= ret;
+			}
+		}
+
+		if (ses_ptr->hdata.init != 0) {
+			if (unlikely(caop->tag_len > sizeof(vhash) || caop->tag_len > len)) {
+				dprintk(1, KERN_ERR, "Illegal tag len size\n");
+				ret = -EINVAL;
+				goto out_err;
+			}
+
+			read_hash( dst_sg, len, vhash, caop->tag_len);
+			len -= caop->tag_len;
+
+			if (auth_len > 0) {
+				ret = cryptodev_hash_update(&ses_ptr->hdata,
+								auth_sg, auth_len);
+				if (unlikely(ret)) {
+					dprintk(0, KERN_ERR, "cryptodev_hash_update: %d\n", ret);
+					goto out_err;
+				}
+			}
+
+			if (len > 0) {
+				ret = cryptodev_hash_update(&ses_ptr->hdata,
+									dst_sg, len);
+				if (unlikely(ret)) {
+					dprintk(0, KERN_ERR, "cryptodev_hash_update: %d\n", ret);
+					goto out_err;
+				}
+			}
+
+			ret = cryptodev_hash_final(&ses_ptr->hdata, hash_output);
+			if (unlikely(ret)) {
+				dprintk(0, KERN_ERR, "cryptodev_hash_final: %d\n", ret);
+				goto out_err;
+			}
+
+			if (memcmp(vhash, hash_output, caop->tag_len) != 0 || fail != 0) {
+				dprintk(1, KERN_ERR, "MAC verification failed\n");
+				ret = -ECANCELED;
+				goto out_err;
+			}
+		}
+	}
+	kcaop->dst_len = len;
+	return 0;
+out_err:
+	return ret;
+}
+
+/* This is the main crypto function - zero-copy edition */
+static int
+__crypto_auth_run_zc(struct csession *ses_ptr, struct kernel_crypt_auth_op *kcaop)
+{
+	struct scatterlist *dst_sg, *auth_sg;
+	struct crypt_auth_op *caop = &kcaop->caop;
+	int ret = 0, pagecount;
+
+	ret = get_userbuf3(ses_ptr, kcaop, &auth_sg, &dst_sg, &pagecount);
+	if (unlikely(ret)) {
+		dprintk(1, KERN_ERR, "Error getting user pages.\n");
+		return ret;
+	}
+
+	if (caop->flags & COP_FLAG_AEAD_TLS_TYPE)
+		ret = tls_auth_n_crypt(ses_ptr, kcaop, auth_sg, caop->auth_len, 
+			   dst_sg, caop->len);
+	else {
+		dprintk(1, KERN_ERR, "Unsupported flag for authenc\n");
+		ret = -EINVAL;
+	}
+
+	release_user_pages(ses_ptr->pages, pagecount);
+	return ret;
+}
+
+
+int crypto_auth_run(struct fcrypt *fcr, struct kernel_crypt_auth_op *kcaop)
+{
+	struct csession *ses_ptr;
+	struct crypt_auth_op *caop = &kcaop->caop;
+	int ret;
+
+	if (unlikely(caop->op != COP_ENCRYPT && caop->op != COP_DECRYPT)) {
+		dprintk(1, KERN_DEBUG, "invalid operation op=%u\n", caop->op);
+		return -EINVAL;
+	}
+
+	/* this also enters ses_ptr->sem */
+	ses_ptr = crypto_get_session_by_sid(fcr, caop->ses);
+	if (unlikely(!ses_ptr)) {
+		dprintk(1, KERN_ERR, "invalid session ID=0x%08X\n", caop->ses);
+		return -EINVAL;
+	}
+
+	if (unlikely(ses_ptr->cdata.init == 0)) {
+		dprintk(1, KERN_ERR, "cipher context not initialized\n");
+		return -EINVAL;
+	}
+
+	if (ses_ptr->hdata.init != 0) {
+		ret = cryptodev_hash_reset(&ses_ptr->hdata);
+		if (unlikely(ret)) {
+			dprintk(1, KERN_ERR,
+				"error in cryptodev_hash_reset()\n");
+			goto out_unlock;
+		}
+	}
+
+	cryptodev_cipher_set_iv(&ses_ptr->cdata, kcaop->iv,
+				min(ses_ptr->cdata.ivsize, kcaop->ivlen));
+
+	if (likely(caop->len || caop->auth_len)) {
+		ret = __crypto_auth_run_zc(ses_ptr, kcaop);
+		if (unlikely(ret))
+			goto out_unlock;
+	}
+
+	cryptodev_cipher_get_iv(&ses_ptr->cdata, kcaop->iv,
+				min(ses_ptr->cdata.ivsize, kcaop->ivlen));
+
+out_unlock:
+	mutex_unlock(&ses_ptr->sem);
+	return ret;
+}
diff --git a/drivers/staging/crypto/cryptodev/cryptodev_int.h b/drivers/staging/crypto/cryptodev/cryptodev_int.h
index 469039810215..f24b3896f833 100644
--- a/drivers/staging/crypto/cryptodev/cryptodev_int.h
+++ b/drivers/staging/crypto/cryptodev/cryptodev_int.h
@@ -2,6 +2,8 @@
 #ifndef CRYPTODEV_INT_H
 # define CRYPTODEV_INT_H
 
+#define CRYPTODEV_STATS
+
 #include <linux/init.h>
 #include <linux/sched.h>
 #include <linux/fs.h>
@@ -50,6 +52,11 @@ struct cipher_data {
 	} async;
 };
 
+struct fcrypt {
+	struct list_head list;
+	struct mutex sem;
+};
+
 int cryptodev_cipher_init(struct cipher_data *out, const char *alg_name,
 						uint8_t *key, size_t keylen);
 void cryptodev_cipher_deinit(struct cipher_data *cdata);
@@ -145,4 +152,51 @@ struct kernel_crypt_op {
 	struct mm_struct *mm;
 };
 
+struct kernel_crypt_auth_op {
+	struct crypt_auth_op caop;
+
+	int dst_len; /* based on src_len + pad + tag */
+	int ivlen;
+	__u8 iv[EALG_MAX_BLOCK_LEN];
+
+	struct task_struct *task;
+	struct mm_struct *mm;
+};
+
+/* auth */
+
+int kcaop_from_user(struct kernel_crypt_auth_op *kcop,
+			struct fcrypt *fcr, void __user *arg);
+int kcaop_to_user(struct kernel_crypt_auth_op *kcaop,
+		struct fcrypt *fcr, void __user *arg);
+int crypto_auth_run(struct fcrypt *fcr, struct kernel_crypt_auth_op *kcaop);
+
+/* other internal structs */
+struct csession {
+	struct list_head entry;
+	struct mutex sem;
+	struct cipher_data cdata;
+	struct hash_data hdata;
+	uint32_t sid;
+	uint32_t alignmask;
+#ifdef CRYPTODEV_STATS
+#if !((COP_ENCRYPT < 2) && (COP_DECRYPT < 2))
+#error Struct csession.stat uses COP_{ENCRYPT,DECRYPT} as indices. Do something!
+#endif
+	unsigned long long stat[2];
+	size_t stat_max_size, stat_count;
+#endif
+	int array_size;
+	struct page **pages;
+	struct scatterlist *sg;
+};
+
+struct csession *crypto_get_session_by_sid(struct fcrypt *fcr, uint32_t sid);
+
+inline static void crypto_put_session(struct csession * ses_ptr)
+{
+	mutex_unlock(&ses_ptr->sem);
+}
+int adjust_sg_array(struct csession * ses, int pagecount);
+
 #endif /* CRYPTODEV_INT_H */
diff --git a/drivers/staging/crypto/cryptodev/cryptodev_main.c b/drivers/staging/crypto/cryptodev/cryptodev_main.c
index 092766cfb4dd..cbb9ef4f4413 100644
--- a/drivers/staging/crypto/cryptodev/cryptodev_main.c
+++ b/drivers/staging/crypto/cryptodev/cryptodev_main.c
@@ -53,8 +53,6 @@ MODULE_LICENSE("GPL");
 
 /* ====== Compile-time config ====== */
 
-#define CRYPTODEV_STATS
-
 /* Default (pre-allocated) and maximum size of the job queue.
  * These are free, pending and done items all together. */
 #define DEF_COP_RINGSIZE 16
@@ -73,11 +71,6 @@ MODULE_PARM_DESC(enable_stats, "collect statictics about cryptodev usage");
 #endif
 
 /* ====== CryptoAPI ====== */
-struct fcrypt {
-	struct list_head list;
-	struct mutex sem;
-};
-
 struct todo_list_item {
 	struct list_head __hook;
 	struct kernel_crypt_op kcop;
@@ -105,25 +98,6 @@ struct crypt_priv {
 		(sg)->dma_address = 0;				\
 	} while (0)
 
-struct csession {
-	struct list_head entry;
-	struct mutex sem;
-	struct cipher_data cdata;
-	struct hash_data hdata;
-	uint32_t sid;
-	uint32_t alignmask;
-#ifdef CRYPTODEV_STATS
-#if !((COP_ENCRYPT < 2) && (COP_DECRYPT < 2))
-#error Struct csession.stat uses COP_{ENCRYPT,DECRYPT} as indices. Do something!
-#endif
-	unsigned long long stat[2];
-	size_t stat_max_size, stat_count;
-#endif
-	int array_size;
-	struct page **pages;
-	struct scatterlist *sg;
-};
-
 /* cryptodev's own workqueue, keeps crypto tasks from disturbing the force */
 static struct workqueue_struct *cryptodev_wq;
 
@@ -423,7 +397,7 @@ crypto_finish_all_sessions(struct fcrypt *fcr)
 }
 
 /* Look up session by session ID. The returned session is locked. */
-static struct csession *
+struct csession *
 crypto_get_session_by_sid(struct fcrypt *fcr, uint32_t sid)
 {
 	struct csession *ses_ptr, *retval = 0;
@@ -583,6 +557,33 @@ int __get_userbuf(uint8_t __user *addr, uint32_t len, int write,
 	return 0;
 }
 
+int adjust_sg_array(struct csession * ses, int pagecount)
+{
+struct scatterlist *sg;
+struct page **pages;
+int array_size;
+
+	for (array_size = ses->array_size; array_size < pagecount;
+	     array_size *= 2)
+		;
+
+	dprintk(2, KERN_DEBUG, "%s: reallocating to %d elements\n",
+			__func__, array_size);
+	pages = krealloc(ses->pages, array_size * sizeof(struct page *),
+			 GFP_KERNEL);
+	if (unlikely(!pages))
+		return -ENOMEM;
+	ses->pages = pages;
+	sg = krealloc(ses->sg, array_size * sizeof(struct scatterlist),
+		      GFP_KERNEL);
+	if (unlikely(!sg))
+		return -ENOMEM;
+	ses->sg = sg;
+	ses->array_size = array_size;
+
+	return 0;
+}
+
 /* make cop->src and cop->dst available in scatterlists */
 static int get_userbuf(struct csession *ses, struct kernel_crypt_op *kcop,
                        struct scatterlist **src_sg, struct scatterlist **dst_sg,
@@ -619,27 +620,9 @@ static int get_userbuf(struct csession *ses, struct kernel_crypt_op *kcop,
 	(*tot_pages) = pagecount = src_pagecount + dst_pagecount;
 
 	if (pagecount > ses->array_size) {
-		struct scatterlist *sg;
-		struct page **pages;
-		int array_size;
-
-		for (array_size = ses->array_size; array_size < pagecount;
-		     array_size *= 2)
-			;
-
-		dprintk(2, KERN_DEBUG, "%s: reallocating to %d elements\n",
-				__func__, array_size);
-		pages = krealloc(ses->pages, array_size * sizeof(struct page *),
-				 GFP_KERNEL);
-		if (unlikely(!pages))
-			return -ENOMEM;
-		ses->pages = pages;
-		sg = krealloc(ses->sg, array_size * sizeof(struct scatterlist),
-			      GFP_KERNEL);
-		if (unlikely(!sg))
-			return -ENOMEM;
-		ses->sg = sg;
-		ses->array_size = array_size;
+		rc = adjust_sg_array(ses, pagecount);
+		if (rc)
+			return rc;
 	}
 
 	rc = __get_userbuf(cop->src, cop->len, write_src, src_pagecount,
@@ -769,7 +752,7 @@ static int crypto_run(struct fcrypt *fcr, struct kernel_crypt_op *kcop)
 #endif
 
 out_unlock:
-	mutex_unlock(&ses_ptr->sem);
+	crypto_put_session(ses_ptr);
 	return ret;
 }
 
@@ -989,7 +972,7 @@ static int fill_kcop_from_cop(struct kernel_crypt_op *kcop, struct fcrypt *fcr)
 	kcop->ivlen = cop->iv ? ses_ptr->cdata.ivsize : 0;
 	kcop->digestsize = 0; /* will be updated during operation */
 
-	mutex_unlock(&ses_ptr->sem);
+	crypto_put_session(ses_ptr);
 
 	kcop->task = current;
 	kcop->mm = current->mm;
@@ -1080,7 +1063,7 @@ static int get_session_info(struct fcrypt *fcr, struct session_info_op *siop)
 
 	siop->alignmask = ses_ptr->alignmask;
 
-	mutex_unlock(&ses_ptr->sem);
+	crypto_put_session(ses_ptr);
 	return 0;
 }
 
@@ -1091,6 +1074,7 @@ cryptodev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg_)
 	int __user *p = arg;
 	struct session_op sop;
 	struct kernel_crypt_op kcop;
+	struct kernel_crypt_auth_op kcaop;
 	struct crypt_priv *pcr = filp->private_data;
 	struct fcrypt *fcr;
 	struct session_info_op siop;
@@ -1141,14 +1125,30 @@ cryptodev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg_)
 			return ret;
 		return copy_to_user(arg, &siop, sizeof(siop));
 	case CIOCCRYPT:
-		if (unlikely(ret = kcop_from_user(&kcop, fcr, arg)))
+		if (unlikely(ret = kcop_from_user(&kcop, fcr, arg))) {
+			dprintk(1, KERN_WARNING, "Error copying from user");
 			return ret;
+		}
 
 		ret = crypto_run(fcr, &kcop);
-		if (unlikely(ret))
+		if (unlikely(ret)) {
+			dprintk(1, KERN_WARNING, "Error in crypto_run");
 			return ret;
+		}
 
 		return kcop_to_user(&kcop, fcr, arg);
+	case CIOCAUTHCRYPT:
+		if (unlikely(ret = kcaop_from_user(&kcaop, fcr, arg))) {
+			dprintk(1, KERN_WARNING, "Error copying from user");
+			return ret;
+		}
+
+		ret = crypto_auth_run(fcr, &kcaop);
+		if (unlikely(ret)) {
+			dprintk(1, KERN_WARNING, "Error in crypto_auth_run");
+			return ret;
+		}
+		return kcaop_to_user(&kcaop, fcr, arg);
 	case CIOCASYNCCRYPT:
 		if (unlikely(ret = kcop_from_user(&kcop, fcr, arg)))
 			return ret;
@@ -1241,12 +1241,16 @@ static int compat_kcop_to_user(struct kernel_crypt_op *kcop,
 	struct compat_crypt_op compat_cop;
 
 	ret = fill_cop_from_kcop(kcop, fcr);
-	if (unlikely(ret))
+	if (unlikely(ret)) {
+		dprintk(1, KERN_WARNING, "Error in fill_cop_from_kcop");
 		return ret;
+	}
 	crypt_op_to_compat(&kcop->cop, &compat_cop);
 
-	if (unlikely(copy_to_user(arg, &compat_cop, sizeof(compat_cop))))
+	if (unlikely(copy_to_user(arg, &compat_cop, sizeof(compat_cop)))) {
+		dprintk(1, KERN_WARNING, "Error copying to user");
 		return -EFAULT;
+	}
 	return 0;
 }
 
diff --git a/drivers/staging/crypto/cryptodev/examples/Makefile b/drivers/staging/crypto/cryptodev/examples/Makefile
index 0443242c82e2..eeb9d62d3e06 100644
--- a/drivers/staging/crypto/cryptodev/examples/Makefile
+++ b/drivers/staging/crypto/cryptodev/examples/Makefile
@@ -1,8 +1,9 @@
 KERNEL_DIR ?= /lib/modules/$(shell uname -r)/build
 KBUILD_CFLAGS += -I..
 
-hostprogs := cipher hmac speed async_cipher async_hmac async_speed sha_speed hashcrypt_speed fullspeed
+hostprogs := cipher cipher-aead hmac speed async_cipher async_hmac async_speed sha_speed hashcrypt_speed fullspeed
 example-cipher-objs := cipher.o
+example-cipher-aead-objs := cipher-aead.o
 example-hmac-objs := hmac.o
 example-speed-objs := speed.c
 example-fullspeed-objs := fullspeed.c
diff --git a/drivers/staging/crypto/cryptodev/examples/cipher-aead.c b/drivers/staging/crypto/cryptodev/examples/cipher-aead.c
new file mode 100644
index 000000000000..8b69cb1d25ab
--- /dev/null
+++ b/drivers/staging/crypto/cryptodev/examples/cipher-aead.c
@@ -0,0 +1,271 @@
+/*
+ * Demo on how to use /dev/crypto device for ciphering.
+ *
+ * Placed under public domain.
+ *
+ */
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <sys/ioctl.h>
+#include <crypto/cryptodev.h>
+
+#define	DATA_SIZE	(8*1024+11)
+#define	BLOCK_SIZE	16
+#define	KEY_SIZE	16
+
+#define MAC_SIZE 20 /* SHA1 */
+
+static int
+get_sha1_hmac(int cfd, void* key, int key_size, void* data, int data_size, void* mac)
+{
+	struct session_op sess;
+	struct crypt_op cryp;
+	int i;
+
+	memset(&sess, 0, sizeof(sess));
+	memset(&cryp, 0, sizeof(cryp));
+
+	sess.cipher = 0;
+	sess.mac = CRYPTO_SHA1_HMAC;
+	sess.mackeylen = key_size;
+	sess.mackey = key;
+	if (ioctl(cfd, CIOCGSESSION, &sess)) {
+		perror("ioctl(CIOCGSESSION)");
+		return 1;
+	}
+
+	/* Encrypt data.in to data.encrypted */
+	cryp.ses = sess.ses;
+	cryp.len = data_size;
+	cryp.src = data;
+	cryp.dst = NULL;
+	cryp.iv = NULL;
+	cryp.mac = mac;
+	cryp.op = COP_ENCRYPT;
+	if (ioctl(cfd, CIOCCRYPT, &cryp)) {
+		perror("ioctl(CIOCCRYPT)");
+		return 1;
+	}
+
+	/* Finish crypto session */
+	if (ioctl(cfd, CIOCFSESSION, &sess.ses)) {
+		perror("ioctl(CIOCFSESSION)");
+		return 1;
+	}
+
+	return 0;
+}
+
+static void print_buf(char* desc, unsigned char* buf, int size)
+{
+int i;
+	fputs(desc, stdout);
+	for (i=0;i<size;i++) {
+		printf("%.2x", (uint8_t)buf[i]);
+	}
+	fputs("\n", stdout);
+}
+
+static int
+test_crypto(int cfd)
+{
+	char plaintext_raw[DATA_SIZE + 63], *plaintext;
+	char ciphertext_raw[DATA_SIZE + 63], *ciphertext;
+	char iv[BLOCK_SIZE];
+	char key[KEY_SIZE];
+	unsigned char sha1mac[20];
+	int pad, i;
+
+	struct session_op sess;
+	struct crypt_op co;
+	struct crypt_auth_op cao;
+#ifdef CIOCGSESSINFO
+	struct session_info_op siop;
+#endif
+
+	memset(&sess, 0, sizeof(sess));
+	memset(&cao, 0, sizeof(cao));
+	memset(&co, 0, sizeof(co));
+
+	memset(key,0x33,  sizeof(key));
+	memset(iv, 0x03,  sizeof(iv));
+
+	/* Get crypto session for AES128 */
+	sess.cipher = CRYPTO_AES_CBC;
+	sess.keylen = KEY_SIZE;
+	sess.key = key;
+
+	sess.mac = CRYPTO_SHA1_HMAC;
+	sess.mackeylen = 16;
+	sess.mackey = (uint8_t*)"\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b";
+
+	if (ioctl(cfd, CIOCGSESSION, &sess)) {
+		perror("ioctl(CIOCGSESSION)");
+		return 1;
+	}
+
+#ifdef CIOCGSESSINFO
+	siop.ses = sess.ses;
+	if (ioctl(cfd, CIOCGSESSINFO, &siop)) {
+		perror("ioctl(CIOCGSESSINFO)");
+		return 1;
+	}
+	printf("requested cipher CRYPTO_AES_CBC/HMAC-SHA1, got %s with driver %s\n",
+			siop.cipher_info.cra_name, siop.cipher_info.cra_driver_name);
+
+	plaintext = (char *)(((unsigned long)plaintext_raw + siop.alignmask) & ~siop.alignmask);
+	ciphertext = (char *)(((unsigned long)ciphertext_raw + siop.alignmask) & ~siop.alignmask);
+#else
+	plaintext = plaintext_raw;
+	ciphertext = ciphertext_raw;
+#endif
+	memset(plaintext, 0x15, DATA_SIZE);
+
+	if (get_sha1_hmac(cfd, sess.mackey, sess.mackeylen, plaintext, DATA_SIZE, sha1mac) != 0) {
+		fprintf(stderr, "SHA1 MAC failed\n");
+		return 1;
+	}
+
+	memcpy(ciphertext, plaintext, DATA_SIZE);
+
+	/* Encrypt data.in to data.encrypted */
+	cao.ses = sess.ses;
+	cao.len = DATA_SIZE;
+	cao.src = ciphertext;
+	cao.dst = ciphertext;
+	cao.iv = iv;
+	cao.op = COP_ENCRYPT;
+	cao.flags = COP_FLAG_AEAD_TLS_TYPE;
+
+	if (ioctl(cfd, CIOCAUTHCRYPT, &cao)) {
+		perror("ioctl(CIOCAUTHCRYPT)");
+		return 1;
+	}
+
+	printf("Original plaintext size: %d, ciphertext: %d\n", DATA_SIZE, cao.len);
+
+	if (ioctl(cfd, CIOCFSESSION, &sess.ses)) {
+		perror("ioctl(CIOCFSESSION)");
+		return 1;
+	}
+
+	/* Get crypto session for AES128 */
+	memset(&sess, 0, sizeof(sess));
+	sess.cipher = CRYPTO_AES_CBC;
+	sess.keylen = KEY_SIZE;
+	sess.key = key;
+
+	if (ioctl(cfd, CIOCGSESSION, &sess)) {
+		perror("ioctl(CIOCGSESSION)");
+		return 1;
+	}
+
+	/* Decrypt data.encrypted to data.decrypted */
+	co.ses = sess.ses;
+	co.len = cao.len;
+	co.src = ciphertext;
+	co.dst = ciphertext;
+	co.iv = iv;
+	co.op = COP_DECRYPT;
+	if (ioctl(cfd, CIOCCRYPT, &co)) {
+		perror("ioctl(CIOCCRYPT)");
+		return 1;
+	}
+
+	/* Verify the result */
+	if (memcmp(plaintext, ciphertext, DATA_SIZE) != 0) {
+		int i;
+		fprintf(stderr,
+			"FAIL: Decrypted data are different from the input data.\n");
+		printf("plaintext:");
+		for (i = 0; i < DATA_SIZE; i++) {
+			if ((i % 30) == 0)
+				printf("\n");
+			printf("%02x ", plaintext[i]);
+		}
+		printf("ciphertext:");
+		for (i = 0; i < DATA_SIZE; i++) {
+			if ((i % 30) == 0)
+				printf("\n");
+			printf("%02x ", ciphertext[i]);
+		}
+		printf("\n");
+		return 1;
+	}
+
+	if (memcmp(&ciphertext[cao.len-MAC_SIZE-1], sha1mac, 20) != 0) {
+		fprintf(stderr, "AEAD SHA1 MAC does not match plain MAC\n");
+		print_buf("SHA1: ", sha1mac, 20);
+		print_buf("SHA1-TLS: ", &ciphertext[cao.len-MAC_SIZE-1], 20);
+		return 1;
+	}
+
+	pad = ciphertext[cao.len-1];
+
+	for (i=0;i<pad;i++)
+		if (ciphertext[cao.len-MAC_SIZE-1-i] != pad) {
+			fprintf(stderr, "Pad does not match (expected %d)\n", pad);
+			print_buf("PAD", &ciphertext[cao.len-MAC_SIZE-1-pad], pad);
+			return 1;
+		}
+
+	printf("Test passed\n");
+
+
+	/* Finish crypto session */
+	if (ioctl(cfd, CIOCFSESSION, &sess.ses)) {
+		perror("ioctl(CIOCFSESSION)");
+		return 1;
+	}
+
+	return 0;
+}
+
+int
+main()
+{
+	int fd = -1, cfd = -1;
+
+	/* Open the crypto device */
+	fd = open("/dev/crypto", O_RDWR, 0);
+	if (fd < 0) {
+		perror("open(/dev/crypto)");
+		return 1;
+	}
+
+	/* Clone file descriptor */
+	if (ioctl(fd, CRIOGET, &cfd)) {
+		perror("ioctl(CRIOGET)");
+		return 1;
+	}
+
+	/* Set close-on-exec (not really neede here) */
+	if (fcntl(cfd, F_SETFD, 1) == -1) {
+		perror("fcntl(F_SETFD)");
+		return 1;
+	}
+
+	/* Run the test itself */
+
+	if (test_crypto(cfd))
+		return 1;
+
+	/* Close cloned descriptor */
+	if (close(cfd)) {
+		perror("close(cfd)");
+		return 1;
+	}
+
+	/* Close the original descriptor */
+	if (close(fd)) {
+		perror("close(fd)");
+		return 1;
+	}
+
+	return 0;
+}
+
-- 
2.1.0

