diff options
author | Jo-Philipp Wich <jow@openwrt.org> | 2010-05-04 21:00:14 +0000 |
---|---|---|
committer | Jo-Philipp Wich <jow@openwrt.org> | 2010-05-04 21:00:14 +0000 |
commit | 0ff83e90ba5f0ed1d900813b470f61527500fdd8 (patch) | |
tree | 24468146cc7991ce8fa77e110d197a03582501bc /target/linux/generic-2.4/files/crypto/ocf/ixp4xx/ixp4xx.c | |
parent | 32e7340bf26d1d59aefb14bf1af4c2e3cb9a9642 (diff) | |
download | master-31e0f0ae-0ff83e90ba5f0ed1d900813b470f61527500fdd8.tar.gz master-31e0f0ae-0ff83e90ba5f0ed1d900813b470f61527500fdd8.tar.bz2 master-31e0f0ae-0ff83e90ba5f0ed1d900813b470f61527500fdd8.zip |
add OCF 20100325 support to brcm-2.4
SVN-Revision: 21357
Diffstat (limited to 'target/linux/generic-2.4/files/crypto/ocf/ixp4xx/ixp4xx.c')
-rw-r--r-- | target/linux/generic-2.4/files/crypto/ocf/ixp4xx/ixp4xx.c | 1324 |
1 files changed, 1324 insertions, 0 deletions
diff --git a/target/linux/generic-2.4/files/crypto/ocf/ixp4xx/ixp4xx.c b/target/linux/generic-2.4/files/crypto/ocf/ixp4xx/ixp4xx.c new file mode 100644 index 0000000000..d5414a4ef5 --- /dev/null +++ b/target/linux/generic-2.4/files/crypto/ocf/ixp4xx/ixp4xx.c @@ -0,0 +1,1324 @@ +/* + * An OCF module that uses Intels IXP CryptACC API to do the crypto. + * This driver requires the IXP400 Access Library that is available + * from Intel in order to operate (or compile). + * + * Written by David McCullough <david_mccullough@mcafee.com> + * Copyright (C) 2006-2010 David McCullough + * Copyright (C) 2004-2005 Intel Corporation. + * + * LICENSE TERMS + * + * The free distribution and use of this software in both source and binary + * form is allowed (with or without changes) provided that: + * + * 1. distributions of this source code include the above copyright + * notice, this list of conditions and the following disclaimer; + * + * 2. distributions in binary form include the above copyright + * notice, this list of conditions and the following disclaimer + * in the documentation and/or other associated materials; + * + * 3. the copyright holder's name is not used to endorse products + * built using this software without specific written permission. + * + * ALTERNATIVELY, provided that this notice is retained in full, this product + * may be distributed under the terms of the GNU General Public License (GPL), + * in which case the provisions of the GPL apply INSTEAD OF those given above. + * + * DISCLAIMER + * + * This software is provided 'as is' with no explicit or implied warranties + * in respect of its properties, including, but not limited to, correctness + * and/or fitness for purpose. + */ + +#ifndef AUTOCONF_INCLUDED +#include <linux/config.h> +#endif +#include <linux/module.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/crypto.h> +#include <linux/interrupt.h> +#include <asm/scatterlist.h> + +#include <IxTypes.h> +#include <IxOsBuffMgt.h> +#include <IxNpeDl.h> +#include <IxCryptoAcc.h> +#include <IxQMgr.h> +#include <IxOsServices.h> +#include <IxOsCacheMMU.h> + +#include <cryptodev.h> +#include <uio.h> + +#ifndef IX_MBUF_PRIV +#define IX_MBUF_PRIV(x) ((x)->priv) +#endif + +struct ixp_data; + +struct ixp_q { + struct list_head ixp_q_list; + struct ixp_data *ixp_q_data; + struct cryptop *ixp_q_crp; + struct cryptodesc *ixp_q_ccrd; + struct cryptodesc *ixp_q_acrd; + IX_MBUF ixp_q_mbuf; + UINT8 *ixp_hash_dest; /* Location for hash in client buffer */ + UINT8 *ixp_hash_src; /* Location of hash in internal buffer */ + unsigned char ixp_q_iv_data[IX_CRYPTO_ACC_MAX_CIPHER_IV_LENGTH]; + unsigned char *ixp_q_iv; +}; + +struct ixp_data { + int ixp_registered; /* is the context registered */ + int ixp_crd_flags; /* detect direction changes */ + + int ixp_cipher_alg; + int ixp_auth_alg; + + UINT32 ixp_ctx_id; + UINT32 ixp_hash_key_id; /* used when hashing */ + IxCryptoAccCtx ixp_ctx; + IX_MBUF ixp_pri_mbuf; + IX_MBUF ixp_sec_mbuf; + + struct work_struct ixp_pending_work; + struct work_struct ixp_registration_work; + struct list_head ixp_q; /* unprocessed requests */ +}; + +#ifdef __ixp46X + +#define MAX_IOP_SIZE 64 /* words */ +#define MAX_OOP_SIZE 128 + +#define MAX_PARAMS 3 + +struct ixp_pkq { + struct list_head pkq_list; + struct cryptkop *pkq_krp; + + IxCryptoAccPkeEauInOperands pkq_op; + IxCryptoAccPkeEauOpResult pkq_result; + + UINT32 pkq_ibuf0[MAX_IOP_SIZE]; + UINT32 pkq_ibuf1[MAX_IOP_SIZE]; + UINT32 pkq_ibuf2[MAX_IOP_SIZE]; + UINT32 pkq_obuf[MAX_OOP_SIZE]; +}; + +static LIST_HEAD(ixp_pkq); /* current PK wait list */ +static struct ixp_pkq *ixp_pk_cur; +static spinlock_t ixp_pkq_lock; + +#endif /* __ixp46X */ + +static int ixp_blocked = 0; + +static int32_t ixp_id = -1; +static struct ixp_data **ixp_sessions = NULL; +static u_int32_t ixp_sesnum = 0; + +static int ixp_process(device_t, struct cryptop *, int); +static int ixp_newsession(device_t, u_int32_t *, struct cryptoini *); +static int ixp_freesession(device_t, u_int64_t); +#ifdef __ixp46X +static int ixp_kprocess(device_t, struct cryptkop *krp, int hint); +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +static kmem_cache_t *qcache; +#else +static struct kmem_cache *qcache; +#endif + +#define debug ixp_debug +static int ixp_debug = 0; +module_param(ixp_debug, int, 0644); +MODULE_PARM_DESC(ixp_debug, "Enable debug"); + +static int ixp_init_crypto = 1; +module_param(ixp_init_crypto, int, 0444); /* RO after load/boot */ +MODULE_PARM_DESC(ixp_init_crypto, "Call ixCryptoAccInit (default is 1)"); + +static void ixp_process_pending(void *arg); +static void ixp_registration(void *arg); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) +static void ixp_process_pending_wq(struct work_struct *work); +static void ixp_registration_wq(struct work_struct *work); +#endif + +/* + * dummy device structure + */ + +static struct { + softc_device_decl sc_dev; +} ixpdev; + +static device_method_t ixp_methods = { + /* crypto device methods */ + DEVMETHOD(cryptodev_newsession, ixp_newsession), + DEVMETHOD(cryptodev_freesession,ixp_freesession), + DEVMETHOD(cryptodev_process, ixp_process), +#ifdef __ixp46X + DEVMETHOD(cryptodev_kprocess, ixp_kprocess), +#endif +}; + +/* + * Generate a new software session. + */ +static int +ixp_newsession(device_t dev, u_int32_t *sid, struct cryptoini *cri) +{ + struct ixp_data *ixp; + u_int32_t i; +#define AUTH_LEN(cri, def) \ + (cri->cri_mlen ? cri->cri_mlen : (def)) + + dprintk("%s():alg %d\n", __FUNCTION__,cri->cri_alg); + if (sid == NULL || cri == NULL) { + dprintk("%s,%d - EINVAL\n", __FILE__, __LINE__); + return EINVAL; + } + + if (ixp_sessions) { + for (i = 1; i < ixp_sesnum; i++) + if (ixp_sessions[i] == NULL) + break; + } else + i = 1; /* NB: to silence compiler warning */ + + if (ixp_sessions == NULL || i == ixp_sesnum) { + struct ixp_data **ixpd; + + if (ixp_sessions == NULL) { + i = 1; /* We leave ixp_sessions[0] empty */ + ixp_sesnum = CRYPTO_SW_SESSIONS; + } else + ixp_sesnum *= 2; + + ixpd = kmalloc(ixp_sesnum * sizeof(struct ixp_data *), SLAB_ATOMIC); + if (ixpd == NULL) { + /* Reset session number */ + if (ixp_sesnum == CRYPTO_SW_SESSIONS) + ixp_sesnum = 0; + else + ixp_sesnum /= 2; + dprintk("%s,%d: ENOBUFS\n", __FILE__, __LINE__); + return ENOBUFS; + } + memset(ixpd, 0, ixp_sesnum * sizeof(struct ixp_data *)); + + /* Copy existing sessions */ + if (ixp_sessions) { + memcpy(ixpd, ixp_sessions, + (ixp_sesnum / 2) * sizeof(struct ixp_data *)); + kfree(ixp_sessions); + } + + ixp_sessions = ixpd; + } + + ixp_sessions[i] = (struct ixp_data *) kmalloc(sizeof(struct ixp_data), + SLAB_ATOMIC); + if (ixp_sessions[i] == NULL) { + ixp_freesession(NULL, i); + dprintk("%s,%d: EINVAL\n", __FILE__, __LINE__); + return ENOBUFS; + } + + *sid = i; + + ixp = ixp_sessions[i]; + memset(ixp, 0, sizeof(*ixp)); + + ixp->ixp_cipher_alg = -1; + ixp->ixp_auth_alg = -1; + ixp->ixp_ctx_id = -1; + INIT_LIST_HEAD(&ixp->ixp_q); + + ixp->ixp_ctx.useDifferentSrcAndDestMbufs = 0; + + while (cri) { + switch (cri->cri_alg) { + case CRYPTO_DES_CBC: + ixp->ixp_cipher_alg = cri->cri_alg; + ixp->ixp_ctx.cipherCtx.cipherAlgo = IX_CRYPTO_ACC_CIPHER_DES; + ixp->ixp_ctx.cipherCtx.cipherMode = IX_CRYPTO_ACC_MODE_CBC; + ixp->ixp_ctx.cipherCtx.cipherKeyLen = (cri->cri_klen + 7) / 8; + ixp->ixp_ctx.cipherCtx.cipherBlockLen = IX_CRYPTO_ACC_DES_BLOCK_64; + ixp->ixp_ctx.cipherCtx.cipherInitialVectorLen = + IX_CRYPTO_ACC_DES_IV_64; + memcpy(ixp->ixp_ctx.cipherCtx.key.cipherKey, + cri->cri_key, (cri->cri_klen + 7) / 8); + break; + + case CRYPTO_3DES_CBC: + ixp->ixp_cipher_alg = cri->cri_alg; + ixp->ixp_ctx.cipherCtx.cipherAlgo = IX_CRYPTO_ACC_CIPHER_3DES; + ixp->ixp_ctx.cipherCtx.cipherMode = IX_CRYPTO_ACC_MODE_CBC; + ixp->ixp_ctx.cipherCtx.cipherKeyLen = (cri->cri_klen + 7) / 8; + ixp->ixp_ctx.cipherCtx.cipherBlockLen = IX_CRYPTO_ACC_DES_BLOCK_64; + ixp->ixp_ctx.cipherCtx.cipherInitialVectorLen = + IX_CRYPTO_ACC_DES_IV_64; + memcpy(ixp->ixp_ctx.cipherCtx.key.cipherKey, + cri->cri_key, (cri->cri_klen + 7) / 8); + break; + + case CRYPTO_RIJNDAEL128_CBC: + ixp->ixp_cipher_alg = cri->cri_alg; + ixp->ixp_ctx.cipherCtx.cipherAlgo = IX_CRYPTO_ACC_CIPHER_AES; + ixp->ixp_ctx.cipherCtx.cipherMode = IX_CRYPTO_ACC_MODE_CBC; + ixp->ixp_ctx.cipherCtx.cipherKeyLen = (cri->cri_klen + 7) / 8; + ixp->ixp_ctx.cipherCtx.cipherBlockLen = 16; + ixp->ixp_ctx.cipherCtx.cipherInitialVectorLen = 16; + memcpy(ixp->ixp_ctx.cipherCtx.key.cipherKey, + cri->cri_key, (cri->cri_klen + 7) / 8); + break; + + case CRYPTO_MD5: + case CRYPTO_MD5_HMAC: + ixp->ixp_auth_alg = cri->cri_alg; + ixp->ixp_ctx.authCtx.authAlgo = IX_CRYPTO_ACC_AUTH_MD5; + ixp->ixp_ctx.authCtx.authDigestLen = AUTH_LEN(cri, MD5_HASH_LEN); + ixp->ixp_ctx.authCtx.aadLen = 0; + /* Only MD5_HMAC needs a key */ + if (cri->cri_alg == CRYPTO_MD5_HMAC) { + ixp->ixp_ctx.authCtx.authKeyLen = (cri->cri_klen + 7) / 8; + if (ixp->ixp_ctx.authCtx.authKeyLen > + sizeof(ixp->ixp_ctx.authCtx.key.authKey)) { + printk( + "ixp4xx: Invalid key length for MD5_HMAC - %d bits\n", + cri->cri_klen); + ixp_freesession(NULL, i); + return EINVAL; + } + memcpy(ixp->ixp_ctx.authCtx.key.authKey, + cri->cri_key, (cri->cri_klen + 7) / 8); + } + break; + + case CRYPTO_SHA1: + case CRYPTO_SHA1_HMAC: + ixp->ixp_auth_alg = cri->cri_alg; + ixp->ixp_ctx.authCtx.authAlgo = IX_CRYPTO_ACC_AUTH_SHA1; + ixp->ixp_ctx.authCtx.authDigestLen = AUTH_LEN(cri, SHA1_HASH_LEN); + ixp->ixp_ctx.authCtx.aadLen = 0; + /* Only SHA1_HMAC needs a key */ + if (cri->cri_alg == CRYPTO_SHA1_HMAC) { + ixp->ixp_ctx.authCtx.authKeyLen = (cri->cri_klen + 7) / 8; + if (ixp->ixp_ctx.authCtx.authKeyLen > + sizeof(ixp->ixp_ctx.authCtx.key.authKey)) { + printk( + "ixp4xx: Invalid key length for SHA1_HMAC - %d bits\n", + cri->cri_klen); + ixp_freesession(NULL, i); + return EINVAL; + } + memcpy(ixp->ixp_ctx.authCtx.key.authKey, + cri->cri_key, (cri->cri_klen + 7) / 8); + } + break; + + default: + printk("ixp: unknown algo 0x%x\n", cri->cri_alg); + ixp_freesession(NULL, i); + return EINVAL; + } + cri = cri->cri_next; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) + INIT_WORK(&ixp->ixp_pending_work, ixp_process_pending_wq); + INIT_WORK(&ixp->ixp_registration_work, ixp_registration_wq); +#else + INIT_WORK(&ixp->ixp_pending_work, ixp_process_pending, ixp); + INIT_WORK(&ixp->ixp_registration_work, ixp_registration, ixp); +#endif + + return 0; +} + + +/* + * Free a session. + */ +static int +ixp_freesession(device_t dev, u_int64_t tid) +{ + u_int32_t sid = CRYPTO_SESID2LID(tid); + + dprintk("%s()\n", __FUNCTION__); + if (sid > ixp_sesnum || ixp_sessions == NULL || + ixp_sessions[sid] == NULL) { + dprintk("%s,%d: EINVAL\n", __FILE__, __LINE__); + return EINVAL; + } + + /* Silently accept and return */ + if (sid == 0) + return 0; + + if (ixp_sessions[sid]) { + if (ixp_sessions[sid]->ixp_ctx_id != -1) { + ixCryptoAccCtxUnregister(ixp_sessions[sid]->ixp_ctx_id); + ixp_sessions[sid]->ixp_ctx_id = -1; + } + kfree(ixp_sessions[sid]); + } + ixp_sessions[sid] = NULL; + if (ixp_blocked) { + ixp_blocked = 0; + crypto_unblock(ixp_id, CRYPTO_SYMQ); + } + return 0; +} + + +/* + * callback for when hash processing is complete + */ + +static void +ixp_hash_perform_cb( + UINT32 hash_key_id, + IX_MBUF *bufp, + IxCryptoAccStatus status) +{ + struct ixp_q *q; + + dprintk("%s(%u, %p, 0x%x)\n", __FUNCTION__, hash_key_id, bufp, status); + + if (bufp == NULL) { + printk("ixp: NULL buf in %s\n", __FUNCTION__); + return; + } + + q = IX_MBUF_PRIV(bufp); + if (q == NULL) { + printk("ixp: NULL priv in %s\n", __FUNCTION__); + return; + } + + if (status == IX_CRYPTO_ACC_STATUS_SUCCESS) { + /* On success, need to copy hash back into original client buffer */ + memcpy(q->ixp_hash_dest, q->ixp_hash_src, + (q->ixp_q_data->ixp_auth_alg == CRYPTO_SHA1) ? + SHA1_HASH_LEN : MD5_HASH_LEN); + } + else { + printk("ixp: hash perform failed status=%d\n", status); + q->ixp_q_crp->crp_etype = EINVAL; + } + + /* Free internal buffer used for hashing */ + kfree(IX_MBUF_MDATA(&q->ixp_q_mbuf)); + + crypto_done(q->ixp_q_crp); + kmem_cache_free(qcache, q); +} + +/* + * setup a request and perform it + */ +static void +ixp_q_process(struct ixp_q *q) +{ + IxCryptoAccStatus status; + struct ixp_data *ixp = q->ixp_q_data; + int auth_off = 0; + int auth_len = 0; + int crypt_off = 0; + int crypt_len = 0; + int icv_off = 0; + char *crypt_func; + + dprintk("%s(%p)\n", __FUNCTION__, q); + + if (q->ixp_q_ccrd) { + if (q->ixp_q_ccrd->crd_flags & CRD_F_IV_EXPLICIT) { + q->ixp_q_iv = q->ixp_q_ccrd->crd_iv; + } else { + q->ixp_q_iv = q->ixp_q_iv_data; + crypto_copydata(q->ixp_q_crp->crp_flags, q->ixp_q_crp->crp_buf, + q->ixp_q_ccrd->crd_inject, + ixp->ixp_ctx.cipherCtx.cipherInitialVectorLen, + (caddr_t) q->ixp_q_iv); + } + + if (q->ixp_q_acrd) { + auth_off = q->ixp_q_acrd->crd_skip; + auth_len = q->ixp_q_acrd->crd_len; + icv_off = q->ixp_q_acrd->crd_inject; + } + + crypt_off = q->ixp_q_ccrd->crd_skip; + crypt_len = q->ixp_q_ccrd->crd_len; + } else { /* if (q->ixp_q_acrd) */ + auth_off = q->ixp_q_acrd->crd_skip; + auth_len = q->ixp_q_acrd->crd_len; + icv_off = q->ixp_q_acrd->crd_inject; + } + + if (q->ixp_q_crp->crp_flags & CRYPTO_F_SKBUF) { + struct sk_buff *skb = (struct sk_buff *) q->ixp_q_crp->crp_buf; + if (skb_shinfo(skb)->nr_frags) { + /* + * DAVIDM fix this limitation one day by using + * a buffer pool and chaining, it is not currently + * needed for current user/kernel space acceleration + */ + printk("ixp: Cannot handle fragmented skb's yet !\n"); + q->ixp_q_crp->crp_etype = ENOENT; + goto done; + } + IX_MBUF_MLEN(&q->ixp_q_mbuf) = + IX_MBUF_PKT_LEN(&q->ixp_q_mbuf) = skb->len; + IX_MBUF_MDATA(&q->ixp_q_mbuf) = skb->data; + } else if (q->ixp_q_crp->crp_flags & CRYPTO_F_IOV) { + struct uio *uiop = (struct uio *) q->ixp_q_crp->crp_buf; + if (uiop->uio_iovcnt != 1) { + /* + * DAVIDM fix this limitation one day by using + * a buffer pool and chaining, it is not currently + * needed for current user/kernel space acceleration + */ + printk("ixp: Cannot handle more than 1 iovec yet !\n"); + q->ixp_q_crp->crp_etype = ENOENT; + goto done; + } + IX_MBUF_MLEN(&q->ixp_q_mbuf) = + IX_MBUF_PKT_LEN(&q->ixp_q_mbuf) = uiop->uio_iov[0].iov_len; + IX_MBUF_MDATA(&q->ixp_q_mbuf) = uiop->uio_iov[0].iov_base; + } else /* contig buffer */ { + IX_MBUF_MLEN(&q->ixp_q_mbuf) = + IX_MBUF_PKT_LEN(&q->ixp_q_mbuf) = q->ixp_q_crp->crp_ilen; + IX_MBUF_MDATA(&q->ixp_q_mbuf) = q->ixp_q_crp->crp_buf; + } + + IX_MBUF_PRIV(&q->ixp_q_mbuf) = q; + + if (ixp->ixp_auth_alg == CRYPTO_SHA1 || ixp->ixp_auth_alg == CRYPTO_MD5) { + /* + * For SHA1 and MD5 hash, need to create an internal buffer that is big + * enough to hold the original data + the appropriate padding for the + * hash algorithm. + */ + UINT8 *tbuf = NULL; + + IX_MBUF_MLEN(&q->ixp_q_mbuf) = IX_MBUF_PKT_LEN(&q->ixp_q_mbuf) = + ((IX_MBUF_MLEN(&q->ixp_q_mbuf) * 8) + 72 + 511) / 8; + tbuf = kmalloc(IX_MBUF_MLEN(&q->ixp_q_mbuf), SLAB_ATOMIC); + + if (IX_MBUF_MDATA(&q->ixp_q_mbuf) == NULL) { + printk("ixp: kmalloc(%u, SLAB_ATOMIC) failed\n", + IX_MBUF_MLEN(&q->ixp_q_mbuf)); + q->ixp_q_crp->crp_etype = ENOMEM; + goto done; + } + memcpy(tbuf, &(IX_MBUF_MDATA(&q->ixp_q_mbuf))[auth_off], auth_len); + + /* Set location in client buffer to copy hash into */ + q->ixp_hash_dest = + &(IX_MBUF_MDATA(&q->ixp_q_mbuf))[auth_off + auth_len]; + + IX_MBUF_MDATA(&q->ixp_q_mbuf) = tbuf; + + /* Set location in internal buffer for where hash starts */ + q->ixp_hash_src = &(IX_MBUF_MDATA(&q->ixp_q_mbuf))[auth_len]; + + crypt_func = "ixCryptoAccHashPerform"; + status = ixCryptoAccHashPerform(ixp->ixp_ctx.authCtx.authAlgo, + &q->ixp_q_mbuf, ixp_hash_perform_cb, 0, auth_len, auth_len, + &ixp->ixp_hash_key_id); + } + else { + crypt_func = "ixCryptoAccAuthCryptPerform"; + status = ixCryptoAccAuthCryptPerform(ixp->ixp_ctx_id, &q->ixp_q_mbuf, + NULL, auth_off, auth_len, crypt_off, crypt_len, icv_off, + q->ixp_q_iv); + } + + if (IX_CRYPTO_ACC_STATUS_SUCCESS == status) + return; + + if (IX_CRYPTO_ACC_STATUS_QUEUE_FULL == status) { + q->ixp_q_crp->crp_etype = ENOMEM; + goto done; + } + + printk("ixp: %s failed %u\n", crypt_func, status); + q->ixp_q_crp->crp_etype = EINVAL; + +done: + crypto_done(q->ixp_q_crp); + kmem_cache_free(qcache, q); +} + + +/* + * because we cannot process the Q from the Register callback + * we do it here on a task Q. + */ + +static void +ixp_process_pending(void *arg) +{ + struct ixp_data *ixp = arg; + struct ixp_q *q = NULL; + + dprintk("%s(%p)\n", __FUNCTION__, arg); + + if (!ixp) + return; + + while (!list_empty(&ixp->ixp_q)) { + q = list_entry(ixp->ixp_q.next, struct ixp_q, ixp_q_list); + list_del(&q->ixp_q_list); + ixp_q_process(q); + } +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) +static void +ixp_process_pending_wq(struct work_struct *work) +{ + struct ixp_data *ixp = container_of(work, struct ixp_data, ixp_pending_work); + ixp_process_pending(ixp); +} +#endif + +/* + * callback for when context registration is complete + */ + +static void +ixp_register_cb(UINT32 ctx_id, IX_MBUF *bufp, IxCryptoAccStatus status) +{ + int i; + struct ixp_data *ixp; + struct ixp_q *q; + + dprintk("%s(%d, %p, %d)\n", __FUNCTION__, ctx_id, bufp, status); + + /* + * free any buffer passed in to this routine + */ + if (bufp) { + IX_MBUF_MLEN(bufp) = IX_MBUF_PKT_LEN(bufp) = 0; + kfree(IX_MBUF_MDATA(bufp)); + IX_MBUF_MDATA(bufp) = NULL; + } + + for (i = 0; i < ixp_sesnum; i++) { + ixp = ixp_sessions[i]; + if (ixp && ixp->ixp_ctx_id == ctx_id) + break; + } + if (i >= ixp_sesnum) { + printk("ixp: invalid context id %d\n", ctx_id); + return; + } + + if (IX_CRYPTO_ACC_STATUS_WAIT == status) { + /* this is normal to free the first of two buffers */ + dprintk("ixp: register not finished yet.\n"); + return; + } + + if (IX_CRYPTO_ACC_STATUS_SUCCESS != status) { + printk("ixp: register failed 0x%x\n", status); + while (!list_empty(&ixp->ixp_q)) { + q = list_entry(ixp->ixp_q.next, struct ixp_q, ixp_q_list); + list_del(&q->ixp_q_list); + q->ixp_q_crp->crp_etype = EINVAL; + crypto_done(q->ixp_q_crp); + kmem_cache_free(qcache, q); + } + return; + } + + /* + * we are now registered, we cannot start processing the Q here + * or we get strange errors with AES (DES/3DES seem to be ok). + */ + ixp->ixp_registered = 1; + schedule_work(&ixp->ixp_pending_work); +} + + +/* + * callback for when data processing is complete + */ + +static void +ixp_perform_cb( + UINT32 ctx_id, + IX_MBUF *sbufp, + IX_MBUF *dbufp, + IxCryptoAccStatus status) +{ + struct ixp_q *q; + + dprintk("%s(%d, %p, %p, 0x%x)\n", __FUNCTION__, ctx_id, sbufp, + dbufp, status); + + if (sbufp == NULL) { + printk("ixp: NULL sbuf in ixp_perform_cb\n"); + return; + } + + q = IX_MBUF_PRIV(sbufp); + if (q == NULL) { + printk("ixp: NULL priv in ixp_perform_cb\n"); + return; + } + + if (status != IX_CRYPTO_ACC_STATUS_SUCCESS) { + printk("ixp: perform failed status=%d\n", status); + q->ixp_q_crp->crp_etype = EINVAL; + } + + crypto_done(q->ixp_q_crp); + kmem_cache_free(qcache, q); +} + + +/* + * registration is not callable at IRQ time, so we defer + * to a task queue, this routines completes the registration for us + * when the task queue runs + * + * Unfortunately this means we cannot tell OCF that the driver is blocked, + * we do that on the next request. + */ + +static void +ixp_registration(void *arg) +{ + struct ixp_data *ixp = arg; + struct ixp_q *q = NULL; + IX_MBUF *pri = NULL, *sec = NULL; + int status = IX_CRYPTO_ACC_STATUS_SUCCESS; + + if (!ixp) { + printk("ixp: ixp_registration with no arg\n"); + return; + } + + if (ixp->ixp_ctx_id != -1) { + ixCryptoAccCtxUnregister(ixp->ixp_ctx_id); + ixp->ixp_ctx_id = -1; + } + + if (list_empty(&ixp->ixp_q)) { + printk("ixp: ixp_registration with no Q\n"); + return; + } + + /* + * setup the primary and secondary buffers + */ + q = list_entry(ixp->ixp_q.next, struct ixp_q, ixp_q_list); + if (q->ixp_q_acrd) { + pri = &ixp->ixp_pri_mbuf; + sec = &ixp->ixp_sec_mbuf; + IX_MBUF_MLEN(pri) = IX_MBUF_PKT_LEN(pri) = 128; + IX_MBUF_MDATA(pri) = (unsigned char *) kmalloc(128, SLAB_ATOMIC); + IX_MBUF_MLEN(sec) = IX_MBUF_PKT_LEN(sec) = 128; + IX_MBUF_MDATA(sec) = (unsigned char *) kmalloc(128, SLAB_ATOMIC); + } + + /* Only need to register if a crypt op or HMAC op */ + if (!(ixp->ixp_auth_alg == CRYPTO_SHA1 || + ixp->ixp_auth_alg == CRYPTO_MD5)) { + status = ixCryptoAccCtxRegister( + &ixp->ixp_ctx, + pri, sec, + ixp_register_cb, + ixp_perform_cb, + &ixp->ixp_ctx_id); + } + else { + /* Otherwise we start processing pending q */ + schedule_work(&ixp->ixp_pending_work); + } + + if (IX_CRYPTO_ACC_STATUS_SUCCESS == status) + return; + + if (IX_CRYPTO_ACC_STATUS_EXCEED_MAX_TUNNELS == status) { + printk("ixp: ixCryptoAccCtxRegister failed (out of tunnels)\n"); + ixp_blocked = 1; + /* perhaps we should return EGAIN on queued ops ? */ + return; + } + + printk("ixp: ixCryptoAccCtxRegister failed %d\n", status); + ixp->ixp_ctx_id = -1; + + /* + * everything waiting is toasted + */ + while (!list_empty(&ixp->ixp_q)) { + q = list_entry(ixp->ixp_q.next, struct ixp_q, ixp_q_list); + list_del(&q->ixp_q_list); + q->ixp_q_crp->crp_etype = ENOENT; + crypto_done(q->ixp_q_crp); + kmem_cache_free(qcache, q); + } +} + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) +static void +ixp_registration_wq(struct work_struct *work) +{ + struct ixp_data *ixp = container_of(work, struct ixp_data, + ixp_registration_work); + ixp_registration(ixp); +} +#endif + +/* + * Process a request. + */ +static int +ixp_process(device_t dev, struct cryptop *crp, int hint) +{ + struct ixp_data *ixp; + unsigned int lid; + struct ixp_q *q = NULL; + int status; + + dprintk("%s()\n", __FUNCTION__); + + /* Sanity check */ + if (crp == NULL) { + dprintk("%s,%d: EINVAL\n", __FILE__, __LINE__); + return EINVAL; + } + + crp->crp_etype = 0; + + if (ixp_blocked) + return ERESTART; + + if (crp->crp_desc == NULL || crp->crp_buf == NULL) { + dprintk("%s,%d: EINVAL\n", __FILE__, __LINE__); + crp->crp_etype = EINVAL; + goto done; + } + + /* + * find the session we are using + */ + + lid = crp->crp_sid & 0xffffffff; + if (lid >= ixp_sesnum || lid == 0 || ixp_sessions == NULL || + ixp_sessions[lid] == NULL) { + crp->crp_etype = ENOENT; + dprintk("%s,%d: ENOENT\n", __FILE__, __LINE__); + goto done; + } + ixp = ixp_sessions[lid]; + + /* + * setup a new request ready for queuing + */ + q = kmem_cache_alloc(qcache, SLAB_ATOMIC); + if (q == NULL) { + dprintk("%s,%d: ENOMEM\n", __FILE__, __LINE__); + crp->crp_etype = ENOMEM; + goto done; + } + /* + * save some cycles by only zeroing the important bits + */ + memset(&q->ixp_q_mbuf, 0, sizeof(q->ixp_q_mbuf)); + q->ixp_q_ccrd = NULL; + q->ixp_q_acrd = NULL; + q->ixp_q_crp = crp; + q->ixp_q_data = ixp; + + /* + * point the cipher and auth descriptors appropriately + * check that we have something to do + */ + if (crp->crp_desc->crd_alg == ixp->ixp_cipher_alg) + q->ixp_q_ccrd = crp->crp_desc; + else if (crp->crp_desc->crd_alg == ixp->ixp_auth_alg) + q->ixp_q_acrd = crp->crp_desc; + else { + crp->crp_etype = ENOENT; + dprintk("%s,%d: bad desc match: ENOENT\n", __FILE__, __LINE__); + goto done; + } + if (crp->crp_desc->crd_next) { + if (crp->crp_desc->crd_next->crd_alg == ixp->ixp_cipher_alg) + q->ixp_q_ccrd = crp->crp_desc->crd_next; + else if (crp->crp_desc->crd_next->crd_alg == ixp->ixp_auth_alg) + q->ixp_q_acrd = crp->crp_desc->crd_next; + else { + crp->crp_etype = ENOENT; + dprintk("%s,%d: bad desc match: ENOENT\n", __FILE__, __LINE__); + goto done; + } + } + + /* + * If there is a direction change for this context then we mark it as + * unregistered and re-register is for the new direction. This is not + * a very expensive operation and currently only tends to happen when + * user-space application are doing benchmarks + * + * DM - we should be checking for pending requests before unregistering. + */ + if (q->ixp_q_ccrd && ixp->ixp_registered && + ixp->ixp_crd_flags != (q->ixp_q_ccrd->crd_flags & CRD_F_ENCRYPT)) { + dprintk("%s - detected direction change on session\n", __FUNCTION__); + ixp->ixp_registered = 0; + } + + /* + * if we are registered, call straight into the perform code + */ + if (ixp->ixp_registered) { + ixp_q_process(q); + return 0; + } + + /* + * the only part of the context not set in newsession is the direction + * dependent parts + */ + if (q->ixp_q_ccrd) { + ixp->ixp_crd_flags = (q->ixp_q_ccrd->crd_flags & CRD_F_ENCRYPT); + if (q->ixp_q_ccrd->crd_flags & CRD_F_ENCRYPT) { + ixp->ixp_ctx.operation = q->ixp_q_acrd ? + IX_CRYPTO_ACC_OP_ENCRYPT_AUTH : IX_CRYPTO_ACC_OP_ENCRYPT; + } else { + ixp->ixp_ctx.operation = q->ixp_q_acrd ? + IX_CRYPTO_ACC_OP_AUTH_DECRYPT : IX_CRYPTO_ACC_OP_DECRYPT; + } + } else { + /* q->ixp_q_acrd must be set if we are here */ + ixp->ixp_ctx.operation = IX_CRYPTO_ACC_OP_AUTH_CALC; + } + + status = list_empty(&ixp->ixp_q); + list_add_tail(&q->ixp_q_list, &ixp->ixp_q); + if (status) + schedule_work(&ixp->ixp_registration_work); + return 0; + +done: + if (q) + kmem_cache_free(qcache, q); + crypto_done(crp); + return 0; +} + + +#ifdef __ixp46X +/* + * key processing support for the ixp465 + */ + + +/* + * copy a BN (LE) into a buffer (BE) an fill out the op appropriately + * assume zeroed and only copy bits that are significant + */ + +static int +ixp_copy_ibuf(struct crparam *p, IxCryptoAccPkeEauOperand *op, UINT32 *buf) +{ + unsigned char *src = (unsigned char *) p->crp_p; + unsigned char *dst; + int len, bits = p->crp_nbits; + + dprintk("%s()\n", __FUNCTION__); + + if (bits > MAX_IOP_SIZE * sizeof(UINT32) * 8) { + dprintk("%s - ibuf too big (%d > %d)\n", __FUNCTION__, + bits, MAX_IOP_SIZE * sizeof(UINT32) * 8); + return -1; + } + + len = (bits + 31) / 32; /* the number UINT32's needed */ + + dst = (unsigned char *) &buf[len]; + dst--; + + while (bits > 0) { + *dst-- = *src++; + bits -= 8; + } + +#if 0 /* no need to zero remaining bits as it is done during request alloc */ + while (dst > (unsigned char *) buf) + *dst-- = '\0'; +#endif + + op->pData = buf; + op->dataLen = len; + return 0; +} + +/* + * copy out the result, be as forgiving as we can about small output buffers + */ + +static int +ixp_copy_obuf(struct crparam *p, IxCryptoAccPkeEauOpResult *op, UINT32 *buf) +{ + unsigned char *dst = (unsigned char *) p->crp_p; + unsigned char *src = (unsigned char *) buf; + int len, z, bits = p->crp_nbits; + + dprintk("%s()\n", __FUNCTION__); + + len = op->dataLen * sizeof(UINT32); + + /* skip leading zeroes to be small buffer friendly */ + z = 0; + while (z < len && src[z] == '\0') + z++; + + src += len; + src--; + len -= z; + + while (len > 0 && bits > 0) { + *dst++ = *src--; + len--; + bits -= 8; + } + + while (bits > 0) { + *dst++ = '\0'; + bits -= 8; + } + + if (len > 0) { + dprintk("%s - obuf is %d (z=%d, ob=%d) bytes too small\n", + __FUNCTION__, len, z, p->crp_nbits / 8); + return -1; + } + + return 0; +} + + +/* + * the parameter offsets for exp_mod + */ + +#define IXP_PARAM_BASE 0 +#define IXP_PARAM_EXP 1 +#define IXP_PARAM_MOD 2 +#define IXP_PARAM_RES 3 + +/* + * key processing complete callback, is also used to start processing + * by passing a NULL for pResult + */ + +static void +ixp_kperform_cb( + IxCryptoAccPkeEauOperation operation, + IxCryptoAccPkeEauOpResult *pResult, + BOOL carryOrBorrow, + IxCryptoAccStatus status) +{ + struct ixp_pkq *q, *tmp; + unsigned long flags; + + dprintk("%s(0x%x, %p, %d, 0x%x)\n", __FUNCTION__, operation, pResult, + carryOrBorrow, status); + + /* handle a completed request */ + if (pResult) { + if (ixp_pk_cur && &ixp_pk_cur->pkq_result == pResult) { + q = ixp_pk_cur; + if (status != IX_CRYPTO_ACC_STATUS_SUCCESS) { + dprintk("%s() - op failed 0x%x\n", __FUNCTION__, status); + q->pkq_krp->krp_status = ERANGE; /* could do better */ + } else { + /* copy out the result */ + if (ixp_copy_obuf(&q->pkq_krp->krp_param[IXP_PARAM_RES], + &q->pkq_result, q->pkq_obuf)) + q->pkq_krp->krp_status = ERANGE; + } + crypto_kdone(q->pkq_krp); + kfree(q); + ixp_pk_cur = NULL; + } else + printk("%s - callback with invalid result pointer\n", __FUNCTION__); + } + + spin_lock_irqsave(&ixp_pkq_lock, flags); + if (ixp_pk_cur || list_empty(&ixp_pkq)) { + spin_unlock_irqrestore(&ixp_pkq_lock, flags); + return; + } + + list_for_each_entry_safe(q, tmp, &ixp_pkq, pkq_list) { + + list_del(&q->pkq_list); + ixp_pk_cur = q; + + spin_unlock_irqrestore(&ixp_pkq_lock, flags); + + status = ixCryptoAccPkeEauPerform( + IX_CRYPTO_ACC_OP_EAU_MOD_EXP, + &q->pkq_op, + ixp_kperform_cb, + &q->pkq_result); + + if (status == IX_CRYPTO_ACC_STATUS_SUCCESS) { + dprintk("%s() - ixCryptoAccPkeEauPerform SUCCESS\n", __FUNCTION__); + return; /* callback will return here for callback */ + } else if (status == IX_CRYPTO_ACC_STATUS_RETRY) { + printk("%s() - ixCryptoAccPkeEauPerform RETRY\n", __FUNCTION__); + } else { + printk("%s() - ixCryptoAccPkeEauPerform failed %d\n", + __FUNCTION__, status); + } + q->pkq_krp->krp_status = ERANGE; /* could do better */ + crypto_kdone(q->pkq_krp); + kfree(q); + spin_lock_irqsave(&ixp_pkq_lock, flags); + } + spin_unlock_irqrestore(&ixp_pkq_lock, flags); +} + + +static int +ixp_kprocess(device_t dev, struct cryptkop *krp, int hint) +{ + struct ixp_pkq *q; + int rc = 0; + unsigned long flags; + + dprintk("%s l1=%d l2=%d l3=%d l4=%d\n", __FUNCTION__, + krp->krp_param[IXP_PARAM_BASE].crp_nbits, + krp->krp_param[IXP_PARAM_EXP].crp_nbits, + krp->krp_param[IXP_PARAM_MOD].crp_nbits, + krp->krp_param[IXP_PARAM_RES].crp_nbits); + + + if (krp->krp_op != CRK_MOD_EXP) { + krp->krp_status = EOPNOTSUPP; + goto err; + } + + q = (struct ixp_pkq *) kmalloc(sizeof(*q), GFP_KERNEL); + if (q == NULL) { + krp->krp_status = ENOMEM; + goto err; + } + + /* + * The PKE engine does not appear to zero the output buffer + * appropriately, so we need to do it all here. + */ + memset(q, 0, sizeof(*q)); + + q->pkq_krp = krp; + INIT_LIST_HEAD(&q->pkq_list); + + if (ixp_copy_ibuf(&krp->krp_param[IXP_PARAM_BASE], &q->pkq_op.modExpOpr.M, + q->pkq_ibuf0)) + rc = 1; + if (!rc && ixp_copy_ibuf(&krp->krp_param[IXP_PARAM_EXP], + &q->pkq_op.modExpOpr.e, q->pkq_ibuf1)) + rc = 2; + if (!rc && ixp_copy_ibuf(&krp->krp_param[IXP_PARAM_MOD], + &q->pkq_op.modExpOpr.N, q->pkq_ibuf2)) + rc = 3; + + if (rc) { + kfree(q); + krp->krp_status = ERANGE; + goto err; + } + + q->pkq_result.pData = q->pkq_obuf; + q->pkq_result.dataLen = + (krp->krp_param[IXP_PARAM_RES].crp_nbits + 31) / 32; + + spin_lock_irqsave(&ixp_pkq_lock, flags); + list_add_tail(&q->pkq_list, &ixp_pkq); + spin_unlock_irqrestore(&ixp_pkq_lock, flags); + + if (!ixp_pk_cur) + ixp_kperform_cb(0, NULL, 0, 0); + return (0); + +err: + crypto_kdone(krp); + return (0); +} + + + +#ifdef CONFIG_OCF_RANDOMHARVEST +/* + * We run the random number generator output through SHA so that it + * is FIPS compliant. + */ + +static volatile int sha_done = 0; +static unsigned char sha_digest[20]; + +static void +ixp_hash_cb(UINT8 *digest, IxCryptoAccStatus status) +{ + dprintk("%s(%p, %d)\n", __FUNCTION__, digest, status); + if (sha_digest != digest) + printk("digest error\n"); + if (IX_CRYPTO_ACC_STATUS_SUCCESS == status) + sha_done = 1; + else + sha_done = -status; +} + +static int +ixp_read_random(void *arg, u_int32_t *buf, int maxwords) +{ + IxCryptoAccStatus status; + int i, n, rc; + + dprintk("%s(%p, %d)\n", __FUNCTION__, buf, maxwords); + memset(buf, 0, maxwords * sizeof(*buf)); + status = ixCryptoAccPkePseudoRandomNumberGet(maxwords, buf); + if (status != IX_CRYPTO_ACC_STATUS_SUCCESS) { + dprintk("%s: ixCryptoAccPkePseudoRandomNumberGet failed %d\n", + __FUNCTION__, status); + return 0; + } + + /* + * run the random data through SHA to make it look more random + */ + + n = sizeof(sha_digest); /* process digest bytes at a time */ + + rc = 0; + for (i = 0; i < maxwords; i += n / sizeof(*buf)) { + if ((maxwords - i) * sizeof(*buf) < n) + n = (maxwords - i) * sizeof(*buf); + sha_done = 0; + status = ixCryptoAccPkeHashPerform(IX_CRYPTO_ACC_AUTH_SHA1, + (UINT8 *) &buf[i], n, ixp_hash_cb, sha_digest); + if (status != IX_CRYPTO_ACC_STATUS_SUCCESS) { + dprintk("ixCryptoAccPkeHashPerform failed %d\n", status); + return -EIO; + } + while (!sha_done) + schedule(); + if (sha_done < 0) { + dprintk("ixCryptoAccPkeHashPerform failed CB %d\n", -sha_done); + return 0; + } + memcpy(&buf[i], sha_digest, n); + rc += n / sizeof(*buf);; + } + + return rc; +} +#endif /* CONFIG_OCF_RANDOMHARVEST */ + +#endif /* __ixp46X */ + + + +/* + * our driver startup and shutdown routines + */ + +static int +ixp_init(void) +{ + dprintk("%s(%p)\n", __FUNCTION__, ixp_init); + + if (ixp_init_crypto && ixCryptoAccInit() != IX_CRYPTO_ACC_STATUS_SUCCESS) + printk("ixCryptoAccInit failed, assuming already initialised!\n"); + + qcache = kmem_cache_create("ixp4xx_q", sizeof(struct ixp_q), 0, + SLAB_HWCACHE_ALIGN, NULL +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23) + , NULL +#endif + ); + if (!qcache) { + printk("failed to create Qcache\n"); + return -ENOENT; + } + + memset(&ixpdev, 0, sizeof(ixpdev)); + softc_device_init(&ixpdev, "ixp4xx", 0, ixp_methods); + + ixp_id = crypto_get_driverid(softc_get_device(&ixpdev), + CRYPTOCAP_F_HARDWARE); + if (ixp_id < 0) + panic("IXP/OCF crypto device cannot initialize!"); + +#define REGISTER(alg) \ + crypto_register(ixp_id,alg,0,0) + + REGISTER(CRYPTO_DES_CBC); + REGISTER(CRYPTO_3DES_CBC); + REGISTER(CRYPTO_RIJNDAEL128_CBC); +#ifdef CONFIG_OCF_IXP4XX_SHA1_MD5 + REGISTER(CRYPTO_MD5); + REGISTER(CRYPTO_SHA1); +#endif + REGISTER(CRYPTO_MD5_HMAC); + REGISTER(CRYPTO_SHA1_HMAC); +#undef REGISTER + +#ifdef __ixp46X + spin_lock_init(&ixp_pkq_lock); + /* + * we do not enable the go fast options here as they can potentially + * allow timing based attacks + * + * http://www.openssl.org/news/secadv_20030219.txt + */ + ixCryptoAccPkeEauExpConfig(0, 0); + crypto_kregister(ixp_id, CRK_MOD_EXP, 0); +#ifdef CONFIG_OCF_RANDOMHARVEST + crypto_rregister(ixp_id, ixp_read_random, NULL); +#endif +#endif + + return 0; +} + +static void +ixp_exit(void) +{ + dprintk("%s()\n", __FUNCTION__); + crypto_unregister_all(ixp_id); + ixp_id = -1; + kmem_cache_destroy(qcache); + qcache = NULL; +} + +module_init(ixp_init); +module_exit(ixp_exit); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("David McCullough <dmccullough@cyberguard.com>"); +MODULE_DESCRIPTION("ixp (OCF module for IXP4xx crypto)"); |