summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cryptodev_main.c372
-rw-r--r--examples/Makefile8
-rw-r--r--examples/speed.c169
3 files changed, 442 insertions, 107 deletions
diff --git a/cryptodev_main.c b/cryptodev_main.c
index 0a23f9492cf..21642e6d977 100644
--- a/cryptodev_main.c
+++ b/cryptodev_main.c
@@ -35,6 +35,7 @@
#include <linux/highmem.h>
#include <linux/random.h>
#include <linux/syscalls.h>
+#include <linux/pagemap.h>
#include "cryptodev.h"
#include <asm/uaccess.h>
#include <asm/ioctl.h>
@@ -64,6 +65,8 @@ module_param(enable_stats, int, 0644);
MODULE_PARM_DESC(enable_stats, "collect statictics about cryptodev usage");
#endif
+#define DEFAULT_PREALLOC_PAGES 16
+
/* ====== CryptoAPI ====== */
struct fcrypt {
struct list_head list;
@@ -96,6 +99,9 @@ struct csession {
unsigned long long stat[2];
size_t stat_max_size, stat_count;
#endif
+ int array_size;
+ struct page **pages;
+ struct scatterlist *sg;
};
/* Prepare session for future use. */
@@ -132,6 +138,9 @@ crypto_create_session(struct fcrypt *fcr, struct session_op *sop)
case CRYPTO_CAMELLIA_CBC:
alg_name = "cbc(camelia)";
break;
+ case CRYPTO_NULL:
+ alg_name = "ecb(cipher_null)";
+ break;
default:
dprintk(1,KERN_DEBUG,"%s: bad cipher: %d\n", __func__, sop->cipher);
return -EINVAL;
@@ -191,13 +200,11 @@ crypto_create_session(struct fcrypt *fcr, struct session_op *sop)
}
/* Create a session and put it to the list. */
- ses_new = kmalloc(sizeof(*ses_new), GFP_KERNEL);
+ ses_new = kzalloc(sizeof(*ses_new), GFP_KERNEL);
if(!ses_new) {
return -ENOMEM;
}
- memset(ses_new, 0, sizeof(*ses_new));
-
/* Set-up crypto transform. */
if (alg_name) {
uint8_t keyp[CRYPTO_CIPHER_MAX_KEY_LEN];
@@ -248,6 +255,19 @@ crypto_create_session(struct fcrypt *fcr, struct session_op *sop)
}
}
+ ses_new->array_size = DEFAULT_PREALLOC_PAGES;
+ dprintk(2, KERN_DEBUG, "%s: preallocating for %d user pages\n",
+ __func__, ses_new->array_size);
+ ses_new->pages = kzalloc(ses_new->array_size *
+ sizeof(struct page *), GFP_KERNEL);
+ ses_new->sg = kzalloc(ses_new->array_size *
+ sizeof(struct scatterlist), GFP_KERNEL);
+ if (ses_new->sg == NULL || ses_new->pages == NULL) {
+ dprintk(0,KERN_DEBUG,"Memory error\n");
+ ret = -ENOMEM;
+ goto error_hash;
+ }
+
/* put the new session to the list */
get_random_bytes(&ses_new->sid, sizeof(ses_new->sid));
init_MUTEX(&ses_new->sem);
@@ -274,6 +294,8 @@ restart:
error_hash:
cryptodev_cipher_deinit( &ses_new->cdata);
+ kfree(ses_new->sg);
+ kfree(ses_new->pages);
error_cipher:
if (ses_new) kfree(ses_new);
@@ -304,6 +326,11 @@ crypto_destroy_session(struct csession *ses_ptr)
#endif
cryptodev_cipher_deinit(&ses_ptr->cdata);
cryptodev_hash_deinit(&ses_ptr->hdata);
+ dprintk(2, KERN_DEBUG, "%s: freeing space for %d user pages\n",
+ __func__, ses_ptr->array_size);
+ kfree(ses_ptr->pages);
+ kfree(ses_ptr->sg);
+
up(&ses_ptr->sem);
kfree(ses_ptr);
}
@@ -372,156 +399,294 @@ crypto_get_session_by_sid(struct fcrypt *fcr, uint32_t sid)
return ses_ptr;
}
+static int
+hash_n_crypt(struct csession *ses_ptr, struct crypt_op *cop,
+ struct scatterlist *src_sg, struct scatterlist *dst_sg, uint32_t len)
+{
+ int ret;
+
+ /* Always hash before encryption and after decryption. Maybe
+ * we should introduce a flag to switch... TBD later on.
+ */
+ if (cop->op == COP_ENCRYPT) {
+ if (ses_ptr->hdata.init != 0) {
+ ret = cryptodev_hash_update(&ses_ptr->hdata, src_sg, len);
+ if (unlikely(ret))
+ goto out_err;
+ }
+ if (ses_ptr->cdata.init != 0) {
+ ret = cryptodev_cipher_encrypt( &ses_ptr->cdata, src_sg, dst_sg, len);
+
+ if (unlikely(ret))
+ goto out_err;
+ }
+ } else {
+ if (ses_ptr->cdata.init != 0) {
+ ret = cryptodev_cipher_decrypt( &ses_ptr->cdata, src_sg, dst_sg, len);
+
+ if (unlikely(ret))
+ goto out_err;
+ }
+
+ if (ses_ptr->hdata.init != 0) {
+ ret = cryptodev_hash_update(&ses_ptr->hdata, dst_sg, len);
+ if (unlikely(ret))
+ goto out_err;
+ }
+ }
+ return 0;
+out_err:
+ dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n",ret);
+ return ret;
+}
+
/* This is the main crypto function - feed it with plaintext
and get a ciphertext (or vice versa :-) */
static int
-crypto_run(struct fcrypt *fcr, struct crypt_op *cop)
+__crypto_run_std(struct csession *ses_ptr, struct crypt_op *cop)
{
char *data;
char __user *src, __user *dst;
struct scatterlist sg;
- struct csession *ses_ptr;
- unsigned int ivsize=0;
size_t nbytes, bufsize;
int ret = 0;
+
+ nbytes = cop->len;
+ data = (char*)__get_free_page(GFP_KERNEL);
+
+ if (unlikely(!data)) {
+ return -ENOMEM;
+ }
+ bufsize = PAGE_SIZE < nbytes ? PAGE_SIZE : nbytes;
+
+ src = cop->src;
+ dst = cop->dst;
+
+ while(nbytes > 0) {
+ size_t current_len = nbytes > bufsize ? bufsize : nbytes;
+
+ ret = copy_from_user(data, src, current_len);
+ if (unlikely(ret))
+ break;
+
+ sg_init_one(&sg, data, current_len);
+
+ ret = hash_n_crypt(ses_ptr, cop, &sg, &sg, current_len);
+
+ if (unlikely(ret))
+ break;
+
+ if (ses_ptr->cdata.init != 0) {
+ ret = copy_to_user(dst, data, current_len);
+ if (unlikely(ret))
+ break;
+ }
+
+ dst += current_len;
+ nbytes -= current_len;
+ src += current_len;
+ }
+
+ free_page((unsigned long)data);
+ return ret;
+}
+
+#ifndef DISABLE_ZCOPY
+
+static void release_user_pages(struct page **pg, int pagecount)
+{
+ while (pagecount--) {
+ if (!PageReserved(pg[pagecount]))
+ SetPageDirty(pg[pagecount]);
+ page_cache_release(pg[pagecount]);
+ }
+}
+
+/* last page - first page + 1 */
+#define PAGECOUNT(buf, buflen) \
+ ((((unsigned long)(buf + buflen - 1) & PAGE_MASK) >> PAGE_SHIFT) - \
+ (((unsigned long) buf & PAGE_MASK) >> PAGE_SHIFT) + 1)
+
+/* offset of buf in it's first page */
+#define PAGEOFFSET(buf) ((unsigned long)buf & ~PAGE_MASK)
+
+/* fetch the pages addr resides in into pg and initialise sg with them */
+static int __get_userbuf(uint8_t *addr, uint32_t len, int write,
+ int pgcount, struct page **pg, struct scatterlist *sg)
+{
+ int ret, pglen, i = 0;
+ struct scatterlist *sgp;
+
+ down_write(&current->mm->mmap_sem);
+ ret = get_user_pages(current, current->mm,
+ (unsigned long)addr, pgcount, write, 0, pg, NULL);
+ up_write(&current->mm->mmap_sem);
+ if (ret != pgcount)
+ return -EINVAL;
+
+ sg_init_table(sg, pgcount);
+
+ pglen = min((ptrdiff_t)(PAGE_SIZE - PAGEOFFSET(addr)), (ptrdiff_t)len);
+ sg_set_page(sg, pg[i++], pglen, PAGEOFFSET(addr));
+
+ len -= pglen;
+ for (sgp = sg_next(sg); len; sgp = sg_next(sgp)) {
+ pglen = min((uint32_t)PAGE_SIZE, len);
+ sg_set_page(sgp, pg[i++], pglen, 0);
+ len -= pglen;
+ }
+ sg_mark_end(sg_last(sg, pgcount));
+ return 0;
+}
+
+/* make cop->src and cop->dst available in scatterlists */
+static int get_userbuf(struct csession *ses,
+ struct crypt_op *cop, struct scatterlist **src_sg,
+ struct scatterlist **dst_sg, int *tot_pages)
+{
+ int src_pagecount, dst_pagecount = 0, pagecount, write_src = 1;
+
+ if (cop->src == NULL) {
+ return -EINVAL;
+ }
+
+ src_pagecount = PAGECOUNT(cop->src, cop->len);
+ if (!ses->cdata.init) { /* hashing only */
+ write_src = 0;
+ } else if (cop->src != cop->dst) { /* non-in-situ transformation */
+ if (cop->dst == NULL) {
+ return -EINVAL;
+ }
+ dst_pagecount = PAGECOUNT(cop->dst, cop->len);
+ write_src = 0;
+ }
+ (*tot_pages) = pagecount = src_pagecount + dst_pagecount;
+
+ if (pagecount > ses->array_size) {
+ while (ses->array_size < pagecount)
+ ses->array_size *= 2;
+
+ dprintk(2, KERN_DEBUG, "%s: reallocating to %d elements\n",
+ __func__, ses->array_size);
+ ses->pages = krealloc(ses->pages, ses->array_size *
+ sizeof(struct page *), GFP_KERNEL);
+ ses->sg = krealloc(ses->sg, ses->array_size *
+ sizeof(struct scatterlist), GFP_KERNEL);
+
+ if (ses->sg == NULL || ses->pages == NULL) {
+ return -ENOMEM;
+ }
+ }
+
+ if (__get_userbuf(cop->src, cop->len, write_src,
+ src_pagecount, ses->pages, ses->sg)) {
+ dprintk(1, KERN_ERR, "failed to get user pages for data input\n");
+ return -EINVAL;
+ }
+ (*src_sg) = (*dst_sg) = ses->sg;
+
+ if (dst_pagecount) {
+ (*dst_sg) = ses->sg + src_pagecount;
+
+ if (__get_userbuf(cop->dst, cop->len, 1, dst_pagecount,
+ ses->pages + src_pagecount, *dst_sg)) {
+ dprintk(1, KERN_ERR, "failed to get user pages for data output\n");
+ release_user_pages(ses->pages, src_pagecount);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+/* This is the main crypto function - zero-copy edition */
+static int
+__crypto_run_zc(struct csession *ses_ptr, struct crypt_op *cop)
+{
+ struct scatterlist *src_sg, *dst_sg;
+ int ret = 0, pagecount;
+
+ ret = get_userbuf(ses_ptr, cop, &src_sg, &dst_sg, &pagecount);
+ if (unlikely(ret)) {
+ dprintk(1, KERN_ERR, "Error getting user pages. Falling back to non zero copy.\n");
+ return __crypto_run_std(ses_ptr, cop);
+ }
+
+ ret = hash_n_crypt(ses_ptr, cop, src_sg, dst_sg, cop->len);
+
+ release_user_pages(ses_ptr->pages, pagecount);
+ return ret;
+}
+
+#endif /* DISABLE_ZCOPY */
+
+static int crypto_run(struct fcrypt *fcr, struct crypt_op *cop)
+{
+ struct csession *ses_ptr;
uint8_t hash_output[AALG_MAX_RESULT_LEN];
+ int ret;
if (unlikely(cop->op != COP_ENCRYPT && cop->op != COP_DECRYPT)) {
dprintk(1, KERN_DEBUG, "invalid operation op=%u\n", cop->op);
return -EINVAL;
}
+ /* this also enters ses_ptr->sem */
ses_ptr = crypto_get_session_by_sid(fcr, cop->ses);
if (unlikely(!ses_ptr)) {
dprintk(1, KERN_ERR, "invalid session ID=0x%08X\n", cop->ses);
return -EINVAL;
}
- nbytes = cop->len;
- data = (char*)__get_free_page(GFP_KERNEL);
-
- if (unlikely(!data)) {
- ret = -ENOMEM;
- goto out_unlock;
- }
- bufsize = PAGE_SIZE < nbytes ? PAGE_SIZE : nbytes;
-
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;
+ goto out_unlock;
}
}
if (ses_ptr->cdata.init != 0) {
int blocksize = ses_ptr->cdata.blocksize;
- if (unlikely(nbytes % blocksize)) {
+ if (unlikely(cop->len % blocksize)) {
dprintk(1, KERN_ERR,
- "data size (%zu) isn't a multiple of block size (%u)\n",
- nbytes, blocksize);
+ "data size (%u) isn't a multiple of block size (%u)\n",
+ cop->len, blocksize);
ret = -EINVAL;
- goto out;
+ goto out_unlock;
}
if (cop->iv) {
uint8_t iv[EALG_MAX_BLOCK_LEN];
- ivsize = min((int)sizeof(iv), ses_ptr->cdata.ivsize);
- if (unlikely(copy_from_user(iv, cop->iv, ivsize))) {
- ret = -EFAULT;
- goto out;
+ ret = copy_from_user(iv, cop->iv, min( (int)sizeof(iv), (ses_ptr->cdata.ivsize)));
+ if (unlikely(ret)) {
+ dprintk(1, KERN_ERR, "error copying IV (%d bytes)\n", min( (int)sizeof(iv), (ses_ptr->cdata.ivsize)));
+ goto out_unlock;
}
- cryptodev_cipher_set_iv(&ses_ptr->cdata, iv, ivsize);
+ cryptodev_cipher_set_iv(&ses_ptr->cdata, iv, ses_ptr->cdata.ivsize);
}
}
- src = cop->src;
- dst = cop->dst;
-
-
- while(nbytes > 0) {
- size_t current_len = nbytes > bufsize ? bufsize : nbytes;
-
- if (unlikely(copy_from_user(data, src, current_len))) {
- ret = -EFAULT;
- goto out;
- }
-
- sg_init_one(&sg, data, current_len);
-
- /* Always hash before encryption and after decryption. Maybe
- * we should introduce a flag to switch... TBD later on.
- */
- if (cop->op == COP_ENCRYPT) {
- if (ses_ptr->hdata.init != 0) {
- ret = cryptodev_hash_update(&ses_ptr->hdata, &sg, current_len);
- if (unlikely(ret)) {
- dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n",ret);
- goto out;
- }
- }
- if (ses_ptr->cdata.init != 0) {
- ret = cryptodev_cipher_encrypt( &ses_ptr->cdata, &sg, &sg, current_len);
-
- if (unlikely(ret)) {
- dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n",ret);
- goto out;
- }
-
- ret = copy_to_user(dst, data, current_len);
- if (unlikely(ret)) {
- ret = -EFAULT;
- goto out;
- }
- dst += current_len;
- }
- } else {
- if (ses_ptr->cdata.init != 0) {
- ret = cryptodev_cipher_decrypt( &ses_ptr->cdata, &sg, &sg, current_len);
-
- if (unlikely(ret)) {
- dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n",ret);
- goto out;
- }
-
- ret = copy_to_user(dst, data, current_len);
- if (unlikely(ret)) {
- ret = -EFAULT;
- goto out;
- }
- dst += current_len;
-
- }
-
- if (ses_ptr->hdata.init != 0) {
- ret = cryptodev_hash_update(&ses_ptr->hdata, &sg, current_len);
- if (unlikely(ret)) {
- dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n",ret);
- goto out;
- }
- }
- }
-
- nbytes -= current_len;
- src += current_len;
- }
+#ifdef DISABLE_ZCOPY
+ ret = __crypto_run_std(ses_ptr, cop);
+#else /* normal */
+ ret = __crypto_run_zc(ses_ptr, cop);
+#endif
+ if (unlikely(ret))
+ goto out_unlock;
if (ses_ptr->hdata.init != 0) {
ret = cryptodev_hash_final(&ses_ptr->hdata, hash_output);
if (unlikely(ret)) {
dprintk(0, KERN_ERR, "CryptoAPI failure: %d\n",ret);
- goto out;
+ goto out_unlock;
}
- ret = copy_to_user(cop->mac, hash_output, ses_ptr->hdata.digestsize);
- if (unlikely(ret)) {
- ret = -EFAULT;
- goto out;
- }
+ if (unlikely(copy_to_user(cop->mac, hash_output, ses_ptr->hdata.digestsize)))
+ goto out_unlock;
}
#if defined(CRYPTODEV_STATS)
@@ -534,15 +699,12 @@ crypto_run(struct fcrypt *fcr, struct crypt_op *cop)
}
#endif
-out:
- free_page((unsigned long)data);
-
out_unlock:
up(&ses_ptr->sem);
-
return ret;
}
+
/* ====== /dev/crypto ====== */
static int
diff --git a/examples/Makefile b/examples/Makefile
index 9dce9b7926a..c65297f54b7 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -1,13 +1,16 @@
CC = gcc
CFLAGS = -Wall -g -O2
-progs := cipher hmac ncr pk
+progs := cipher hmac ncr pk speed
all: $(progs)
cipher: cipher.c
$(CC) $(CFLAGS) $< -o $@
+speed: speed.c
+ $(CC) $(CFLAGS) $< -o $@
+
hmac: hmac.c
$(CC) $(CFLAGS) $< -o $@
@@ -22,6 +25,7 @@ check: $(progs)
./pk
./cipher
./hmac
+ ./speed
clean:
- rm -f *.o *~ hmac cipher ncr pk
+ rm -f *.o *~ hmac cipher ncr pk speed
diff --git a/examples/speed.c b/examples/speed.c
new file mode 100644
index 00000000000..bf19863017d
--- /dev/null
+++ b/examples/speed.c
@@ -0,0 +1,169 @@
+/* cryptodev_test - simple benchmark tool for cryptodev
+ *
+ * Copyright (C) 2010 by Phil Sutter <phil.sutter@viprinet.com>
+ *
+ * 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <unistd.h>
+#include "../cryptodev.h"
+
+static double udifftimeval(struct timeval start, struct timeval end)
+{
+ return (double)(end.tv_usec - start.tv_usec) +
+ (double)(end.tv_sec - start.tv_sec) * 1000 * 1000;
+}
+
+static int must_finish = 0;
+
+static void alarm_handler(int signo)
+{
+ must_finish = 1;
+}
+
+static void value2human(double bytes, double time, double* data, double* speed,char* metric)
+{
+ if (bytes > 1000 && bytes < 1000*1000) {
+ *data = ((double)bytes)/1000;
+ *speed = *data/time;
+ strcpy(metric, "Kb");
+ return;
+ } else if (bytes >= 1000*1000 && bytes < 1000*1000*1000) {
+ *data = ((double)bytes)/(1000*1000);
+ *speed = *data/time;
+ strcpy(metric, "Mb");
+ return;
+ } else if (bytes >= 1000*1000*1000) {
+ *data = ((double)bytes)/(1000*1000*1000);
+ *speed = *data/time;
+ strcpy(metric, "Gb");
+ return;
+ } else {
+ *data = (double)bytes;
+ *speed = *data/time;
+ strcpy(metric, "bytes");
+ return;
+ }
+}
+
+
+int encrypt_data(struct session_op *sess, int fdc, int chunksize, int flags)
+{
+ struct crypt_op cop;
+ char *buffer, iv[32];
+ static int val = 23;
+ struct timeval start, end;
+ double total = 0;
+ double secs, ddata, dspeed;
+ char metric[16];
+
+ buffer = malloc(chunksize);
+ memset(iv, 0x23, 32);
+
+ printf("\tEncrypting in chunks of %d bytes: ", chunksize);
+ fflush(stdout);
+
+ memset(buffer, val++, chunksize);
+
+ must_finish = 0;
+ alarm(5);
+
+ gettimeofday(&start, NULL);
+ do {
+ memset(&cop, 0, sizeof(cop));
+ cop.ses = sess->ses;
+ cop.len = chunksize;
+ cop.iv = (unsigned char *)iv;
+ cop.op = COP_ENCRYPT;
+ cop.flags = flags;
+ cop.src = cop.dst = (unsigned char *)buffer;
+
+ if (ioctl(fdc, CIOCCRYPT, &cop)) {
+ perror("ioctl(CIOCCRYPT)");
+ return 1;
+ }
+ total+=chunksize;
+ } while(must_finish==0);
+ gettimeofday(&end, NULL);
+
+ secs = udifftimeval(start, end)/ 1000000.0;
+
+ value2human(total, secs, &ddata, &dspeed, metric);
+ printf ("done. %.2f %s in %.2f secs: ", ddata, metric, secs);
+ printf ("%.2f %s/sec\n", dspeed, metric);
+
+ return 0;
+}
+
+int main(void)
+{
+ int fd, i, fdc = -1;
+ struct session_op sess;
+ char keybuf[32];
+
+ signal(SIGALRM, alarm_handler);
+
+ if ((fd = open("/dev/crypto", O_RDWR, 0)) < 0) {
+ perror("open()");
+ return 1;
+ }
+ if (ioctl(fd, CRIOGET, &fdc)) {
+ perror("ioctl(CRIOGET)");
+ return 1;
+ }
+
+ fprintf(stderr, "Testing NULL cipher: \n");
+ memset(&sess, 0, sizeof(sess));
+ sess.cipher = CRYPTO_NULL;
+ sess.keylen = 0;
+ sess.key = (unsigned char *)keybuf;
+ if (ioctl(fdc, CIOCGSESSION, &sess)) {
+ perror("ioctl(CIOCGSESSION)");
+ return 1;
+ }
+
+ for (i = 256; i <= (64 * 1024); i *= 2) {
+ if (encrypt_data(&sess, fdc, i, 0))
+ break;
+ }
+
+ fprintf(stderr, "\nTesting AES-128-CBC cipher: \n");
+ memset(&sess, 0, sizeof(sess));
+ sess.cipher = CRYPTO_AES_CBC;
+ sess.keylen = 16;
+ memset(keybuf, 0x42, 16);
+ sess.key = (unsigned char *)keybuf;
+ if (ioctl(fdc, CIOCGSESSION, &sess)) {
+ perror("ioctl(CIOCGSESSION)");
+ return 1;
+ }
+
+ for (i = 256; i <= (64 * 1024); i *= 2) {
+ if (encrypt_data(&sess, fdc, i, 0))
+ break;
+ }
+
+ close(fdc);
+ close(fd);
+ return 0;
+}