| /* |
| * Octeon Crypto for OCF |
| * |
| * Written by David McCullough <david_mccullough@mcafee.com> |
| * Copyright (C) 2009-2010 David McCullough |
| * |
| * 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. |
| * |
| * 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. |
| * --------------------------------------------------------------------------- |
| */ |
| |
| #include <linux/version.h> |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38) && !defined(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/mm.h> |
| #include <linux/skbuff.h> |
| #include <linux/random.h> |
| #include <linux/scatterlist.h> |
| |
| #include <cryptodev.h> |
| #include <uio.h> |
| |
| struct { |
| softc_device_decl sc_dev; |
| } octo_softc; |
| |
| #define offset_in_page(p) ((unsigned long)(p) & ~PAGE_MASK) |
| |
| struct octo_sess { |
| int octo_encalg; |
| #define MAX_CIPHER_KEYLEN 64 |
| char octo_enckey[MAX_CIPHER_KEYLEN]; |
| int octo_encklen; |
| |
| int octo_macalg; |
| #define MAX_HASH_KEYLEN 64 |
| char octo_mackey[MAX_HASH_KEYLEN]; |
| int octo_macklen; |
| int octo_mackey_set; |
| |
| int octo_mlen; |
| int octo_ivsize; |
| |
| int (*octo_encrypt)(struct octo_sess *od, |
| struct scatterlist *sg, int sg_len, |
| int auth_off, int auth_len, |
| int crypt_off, int crypt_len, |
| int icv_off, uint8_t *ivp); |
| int (*octo_decrypt)(struct octo_sess *od, |
| struct scatterlist *sg, int sg_len, |
| int auth_off, int auth_len, |
| int crypt_off, int crypt_len, |
| int icv_off, uint8_t *ivp); |
| |
| uint64_t octo_hminner[3]; |
| uint64_t octo_hmouter[3]; |
| }; |
| |
| int32_t octo_id = -1; |
| module_param(octo_id, int, 0444); |
| MODULE_PARM_DESC(octo_id, "Read-Only OCF ID for cryptocteon driver"); |
| |
| static struct octo_sess **octo_sessions = NULL; |
| static u_int32_t octo_sesnum = 0; |
| |
| static int octo_process(device_t, struct cryptop *, int); |
| static int octo_newsession(device_t, u_int32_t *, struct cryptoini *); |
| static int octo_freesession(device_t, u_int64_t); |
| |
| static device_method_t octo_methods = { |
| /* crypto device methods */ |
| DEVMETHOD(cryptodev_newsession, octo_newsession), |
| DEVMETHOD(cryptodev_freesession,octo_freesession), |
| DEVMETHOD(cryptodev_process, octo_process), |
| }; |
| |
| #define debug octo_debug |
| int octo_debug = 0; |
| module_param(octo_debug, int, 0644); |
| MODULE_PARM_DESC(octo_debug, "Enable debug"); |
| |
| |
| #include "cavium_crypto.c" |
| |
| |
| /* |
| * Generate a new octo session. We artifically limit it to a single |
| * hash/cipher or hash-cipher combo just to make it easier, most callers |
| * do not expect more than this anyway. |
| */ |
| static int |
| octo_newsession(device_t dev, u_int32_t *sid, struct cryptoini *cri) |
| { |
| struct cryptoini *c, *encini = NULL, *macini = NULL; |
| struct octo_sess **ocd; |
| int i; |
| |
| dprintk("%s()\n", __FUNCTION__); |
| if (sid == NULL || cri == NULL) { |
| dprintk("%s,%d - EINVAL\n", __FILE__, __LINE__); |
| return EINVAL; |
| } |
| |
| /* |
| * To keep it simple, we only handle hash, cipher or hash/cipher in a |
| * session, you cannot currently do multiple ciphers/hashes in one |
| * session even though it would be possibel to code this driver to |
| * handle it. |
| */ |
| for (i = 0, c = cri; c && i < 2; i++) { |
| if (c->cri_alg == CRYPTO_MD5_HMAC || |
| c->cri_alg == CRYPTO_SHA1_HMAC || |
| c->cri_alg == CRYPTO_NULL_HMAC) { |
| if (macini) { |
| break; |
| } |
| macini = c; |
| } |
| if (c->cri_alg == CRYPTO_DES_CBC || |
| c->cri_alg == CRYPTO_3DES_CBC || |
| c->cri_alg == CRYPTO_AES_CBC || |
| c->cri_alg == CRYPTO_NULL_CBC) { |
| if (encini) { |
| break; |
| } |
| encini = c; |
| } |
| c = c->cri_next; |
| } |
| if (!macini && !encini) { |
| dprintk("%s,%d - EINVAL bad cipher/hash or combination\n", |
| __FILE__, __LINE__); |
| return EINVAL; |
| } |
| if (c) { |
| dprintk("%s,%d - EINVAL cannot handle chained cipher/hash combos\n", |
| __FILE__, __LINE__); |
| return EINVAL; |
| } |
| |
| /* |
| * So we have something we can do, lets setup the session |
| */ |
| |
| if (octo_sessions) { |
| for (i = 1; i < octo_sesnum; i++) |
| if (octo_sessions[i] == NULL) |
| break; |
| } else |
| i = 1; /* NB: to silence compiler warning */ |
| |
| if (octo_sessions == NULL || i == octo_sesnum) { |
| if (octo_sessions == NULL) { |
| i = 1; /* We leave octo_sessions[0] empty */ |
| octo_sesnum = CRYPTO_SW_SESSIONS; |
| } else |
| octo_sesnum *= 2; |
| |
| ocd = kmalloc(octo_sesnum * sizeof(struct octo_sess *), SLAB_ATOMIC); |
| if (ocd == NULL) { |
| /* Reset session number */ |
| if (octo_sesnum == CRYPTO_SW_SESSIONS) |
| octo_sesnum = 0; |
| else |
| octo_sesnum /= 2; |
| dprintk("%s,%d: ENOBUFS\n", __FILE__, __LINE__); |
| return ENOBUFS; |
| } |
| memset(ocd, 0, octo_sesnum * sizeof(struct octo_sess *)); |
| |
| /* Copy existing sessions */ |
| if (octo_sessions) { |
| memcpy(ocd, octo_sessions, |
| (octo_sesnum / 2) * sizeof(struct octo_sess *)); |
| kfree(octo_sessions); |
| } |
| |
| octo_sessions = ocd; |
| } |
| |
| ocd = &octo_sessions[i]; |
| *sid = i; |
| |
| |
| *ocd = (struct octo_sess *) kmalloc(sizeof(struct octo_sess), SLAB_ATOMIC); |
| if (*ocd == NULL) { |
| octo_freesession(NULL, i); |
| dprintk("%s,%d: ENOBUFS\n", __FILE__, __LINE__); |
| return ENOBUFS; |
| } |
| memset(*ocd, 0, sizeof(struct octo_sess)); |
| |
| if (encini && encini->cri_key) { |
| (*ocd)->octo_encklen = (encini->cri_klen + 7) / 8; |
| memcpy((*ocd)->octo_enckey, encini->cri_key, (*ocd)->octo_encklen); |
| } |
| |
| if (macini && macini->cri_key) { |
| (*ocd)->octo_macklen = (macini->cri_klen + 7) / 8; |
| memcpy((*ocd)->octo_mackey, macini->cri_key, (*ocd)->octo_macklen); |
| } |
| |
| (*ocd)->octo_mlen = 0; |
| if (encini && encini->cri_mlen) |
| (*ocd)->octo_mlen = encini->cri_mlen; |
| else if (macini && macini->cri_mlen) |
| (*ocd)->octo_mlen = macini->cri_mlen; |
| else |
| (*ocd)->octo_mlen = 12; |
| |
| /* |
| * point c at the enc if it exists, otherwise the mac |
| */ |
| c = encini ? encini : macini; |
| |
| switch (c->cri_alg) { |
| case CRYPTO_DES_CBC: |
| case CRYPTO_3DES_CBC: |
| (*ocd)->octo_ivsize = 8; |
| switch (macini ? macini->cri_alg : -1) { |
| case CRYPTO_MD5_HMAC: |
| (*ocd)->octo_encrypt = octo_des_cbc_md5_encrypt; |
| (*ocd)->octo_decrypt = octo_des_cbc_md5_decrypt; |
| octo_calc_hash(0, macini->cri_key, (*ocd)->octo_hminner, |
| (*ocd)->octo_hmouter); |
| break; |
| case CRYPTO_SHA1_HMAC: |
| (*ocd)->octo_encrypt = octo_des_cbc_sha1_encrypt; |
| (*ocd)->octo_decrypt = octo_des_cbc_sha1_decrypt; |
| octo_calc_hash(1, macini->cri_key, (*ocd)->octo_hminner, |
| (*ocd)->octo_hmouter); |
| break; |
| case -1: |
| (*ocd)->octo_encrypt = octo_des_cbc_encrypt; |
| (*ocd)->octo_decrypt = octo_des_cbc_decrypt; |
| break; |
| default: |
| octo_freesession(NULL, i); |
| dprintk("%s,%d: EINVALn", __FILE__, __LINE__); |
| return EINVAL; |
| } |
| break; |
| case CRYPTO_AES_CBC: |
| (*ocd)->octo_ivsize = 16; |
| switch (macini ? macini->cri_alg : -1) { |
| case CRYPTO_MD5_HMAC: |
| (*ocd)->octo_encrypt = octo_aes_cbc_md5_encrypt; |
| (*ocd)->octo_decrypt = octo_aes_cbc_md5_decrypt; |
| octo_calc_hash(0, macini->cri_key, (*ocd)->octo_hminner, |
| (*ocd)->octo_hmouter); |
| break; |
| case CRYPTO_SHA1_HMAC: |
| (*ocd)->octo_encrypt = octo_aes_cbc_sha1_encrypt; |
| (*ocd)->octo_decrypt = octo_aes_cbc_sha1_decrypt; |
| octo_calc_hash(1, macini->cri_key, (*ocd)->octo_hminner, |
| (*ocd)->octo_hmouter); |
| break; |
| case -1: |
| (*ocd)->octo_encrypt = octo_aes_cbc_encrypt; |
| (*ocd)->octo_decrypt = octo_aes_cbc_decrypt; |
| break; |
| default: |
| octo_freesession(NULL, i); |
| dprintk("%s,%d: EINVALn", __FILE__, __LINE__); |
| return EINVAL; |
| } |
| break; |
| case CRYPTO_MD5_HMAC: |
| (*ocd)->octo_encrypt = octo_null_md5_encrypt; |
| (*ocd)->octo_decrypt = octo_null_md5_encrypt; /* encrypt == decrypt */ |
| octo_calc_hash(0, macini->cri_key, (*ocd)->octo_hminner, |
| (*ocd)->octo_hmouter); |
| break; |
| case CRYPTO_SHA1_HMAC: |
| (*ocd)->octo_encrypt = octo_null_sha1_encrypt; |
| (*ocd)->octo_decrypt = octo_null_sha1_encrypt; /* encrypt == decrypt */ |
| octo_calc_hash(1, macini->cri_key, (*ocd)->octo_hminner, |
| (*ocd)->octo_hmouter); |
| break; |
| default: |
| octo_freesession(NULL, i); |
| dprintk("%s,%d: EINVALn", __FILE__, __LINE__); |
| return EINVAL; |
| } |
| |
| (*ocd)->octo_encalg = encini ? encini->cri_alg : -1; |
| (*ocd)->octo_macalg = macini ? macini->cri_alg : -1; |
| |
| return 0; |
| } |
| |
| /* |
| * Free a session. |
| */ |
| static int |
| octo_freesession(device_t dev, u_int64_t tid) |
| { |
| u_int32_t sid = CRYPTO_SESID2LID(tid); |
| |
| dprintk("%s()\n", __FUNCTION__); |
| if (sid > octo_sesnum || octo_sessions == NULL || |
| octo_sessions[sid] == NULL) { |
| dprintk("%s,%d: EINVAL\n", __FILE__, __LINE__); |
| return(EINVAL); |
| } |
| |
| /* Silently accept and return */ |
| if (sid == 0) |
| return(0); |
| |
| if (octo_sessions[sid]) |
| kfree(octo_sessions[sid]); |
| octo_sessions[sid] = NULL; |
| return 0; |
| } |
| |
| /* |
| * Process a request. |
| */ |
| static int |
| octo_process(device_t dev, struct cryptop *crp, int hint) |
| { |
| struct cryptodesc *crd; |
| struct octo_sess *od; |
| u_int32_t lid; |
| #define SCATTERLIST_MAX 16 |
| struct scatterlist sg[SCATTERLIST_MAX]; |
| int sg_num, sg_len; |
| struct sk_buff *skb = NULL; |
| struct uio *uiop = NULL; |
| struct cryptodesc *enccrd = NULL, *maccrd = NULL; |
| unsigned char *ivp = NULL; |
| unsigned char iv_data[HASH_MAX_LEN]; |
| int auth_off = 0, auth_len = 0, crypt_off = 0, crypt_len = 0, icv_off = 0; |
| |
| dprintk("%s()\n", __FUNCTION__); |
| /* Sanity check */ |
| if (crp == NULL) { |
| dprintk("%s,%d: EINVAL\n", __FILE__, __LINE__); |
| return EINVAL; |
| } |
| |
| crp->crp_etype = 0; |
| |
| if (crp->crp_desc == NULL || crp->crp_buf == NULL) { |
| dprintk("%s,%d: EINVAL\n", __FILE__, __LINE__); |
| crp->crp_etype = EINVAL; |
| goto done; |
| } |
| |
| lid = crp->crp_sid & 0xffffffff; |
| if (lid >= octo_sesnum || lid == 0 || octo_sessions == NULL || |
| octo_sessions[lid] == NULL) { |
| crp->crp_etype = ENOENT; |
| dprintk("%s,%d: ENOENT\n", __FILE__, __LINE__); |
| goto done; |
| } |
| od = octo_sessions[lid]; |
| |
| /* |
| * do some error checking outside of the loop for SKB and IOV processing |
| * this leaves us with valid skb or uiop pointers for later |
| */ |
| if (crp->crp_flags & CRYPTO_F_SKBUF) { |
| skb = (struct sk_buff *) crp->crp_buf; |
| if (skb_shinfo(skb)->nr_frags >= SCATTERLIST_MAX) { |
| printk("%s,%d: %d nr_frags > SCATTERLIST_MAX", __FILE__, __LINE__, |
| skb_shinfo(skb)->nr_frags); |
| goto done; |
| } |
| } else if (crp->crp_flags & CRYPTO_F_IOV) { |
| uiop = (struct uio *) crp->crp_buf; |
| if (uiop->uio_iovcnt > SCATTERLIST_MAX) { |
| printk("%s,%d: %d uio_iovcnt > SCATTERLIST_MAX", __FILE__, __LINE__, |
| uiop->uio_iovcnt); |
| goto done; |
| } |
| } |
| |
| /* point our enccrd and maccrd appropriately */ |
| crd = crp->crp_desc; |
| if (crd->crd_alg == od->octo_encalg) enccrd = crd; |
| if (crd->crd_alg == od->octo_macalg) maccrd = crd; |
| crd = crd->crd_next; |
| if (crd) { |
| if (crd->crd_alg == od->octo_encalg) enccrd = crd; |
| if (crd->crd_alg == od->octo_macalg) maccrd = crd; |
| crd = crd->crd_next; |
| } |
| if (crd) { |
| crp->crp_etype = EINVAL; |
| dprintk("%s,%d: ENOENT - descriptors do not match session\n", |
| __FILE__, __LINE__); |
| goto done; |
| } |
| |
| if (enccrd) { |
| if (enccrd->crd_flags & CRD_F_ENCRYPT) { |
| if (enccrd->crd_flags & CRD_F_IV_EXPLICIT) |
| ivp = enccrd->crd_iv; |
| else |
| read_random((ivp = iv_data), od->octo_ivsize); |
| if ((enccrd->crd_flags & CRD_F_IV_PRESENT) == 0) |
| crypto_copyback(crp->crp_flags, crp->crp_buf, |
| enccrd->crd_inject, od->octo_ivsize, ivp); |
| } else { |
| if (enccrd->crd_flags & CRD_F_IV_EXPLICIT) { |
| ivp = enccrd->crd_iv; |
| } else { |
| ivp = iv_data; |
| crypto_copydata(crp->crp_flags, crp->crp_buf, |
| enccrd->crd_inject, od->octo_ivsize, (caddr_t) ivp); |
| } |
| } |
| |
| if (maccrd) { |
| auth_off = maccrd->crd_skip; |
| auth_len = maccrd->crd_len; |
| icv_off = maccrd->crd_inject; |
| } |
| |
| crypt_off = enccrd->crd_skip; |
| crypt_len = enccrd->crd_len; |
| } else { /* if (maccrd) */ |
| auth_off = maccrd->crd_skip; |
| auth_len = maccrd->crd_len; |
| icv_off = maccrd->crd_inject; |
| } |
| |
| |
| /* |
| * setup the SG list to cover the buffer |
| */ |
| memset(sg, 0, sizeof(sg)); |
| if (crp->crp_flags & CRYPTO_F_SKBUF) { |
| int i, len; |
| |
| sg_num = 0; |
| sg_len = 0; |
| |
| len = skb_headlen(skb); |
| sg_set_page(&sg[sg_num], virt_to_page(skb->data), len, |
| offset_in_page(skb->data)); |
| sg_len += len; |
| sg_num++; |
| |
| for (i = 0; i < skb_shinfo(skb)->nr_frags && sg_num < SCATTERLIST_MAX; |
| i++) { |
| len = skb_shinfo(skb)->frags[i].size; |
| sg_set_page(&sg[sg_num], skb_frag_page(&skb_shinfo(skb)->frags[i]), |
| len, skb_shinfo(skb)->frags[i].page_offset); |
| sg_len += len; |
| sg_num++; |
| } |
| } else if (crp->crp_flags & CRYPTO_F_IOV) { |
| int len; |
| |
| sg_len = 0; |
| for (sg_num = 0; sg_len < crp->crp_ilen && |
| sg_num < uiop->uio_iovcnt && |
| sg_num < SCATTERLIST_MAX; sg_num++) { |
| len = uiop->uio_iov[sg_num].iov_len; |
| sg_set_page(&sg[sg_num], |
| virt_to_page(uiop->uio_iov[sg_num].iov_base), len, |
| offset_in_page(uiop->uio_iov[sg_num].iov_base)); |
| sg_len += len; |
| } |
| } else { |
| sg_len = crp->crp_ilen; |
| sg_set_page(&sg[0], virt_to_page(crp->crp_buf), sg_len, |
| offset_in_page(crp->crp_buf)); |
| sg_num = 1; |
| } |
| if (sg_num > 0) |
| sg_mark_end(&sg[sg_num-1]); |
| |
| /* |
| * setup a new explicit key |
| */ |
| if (enccrd) { |
| if (enccrd->crd_flags & CRD_F_KEY_EXPLICIT) { |
| od->octo_encklen = (enccrd->crd_klen + 7) / 8; |
| memcpy(od->octo_enckey, enccrd->crd_key, od->octo_encklen); |
| } |
| } |
| if (maccrd) { |
| if (maccrd->crd_flags & CRD_F_KEY_EXPLICIT) { |
| od->octo_macklen = (maccrd->crd_klen + 7) / 8; |
| memcpy(od->octo_mackey, maccrd->crd_key, od->octo_macklen); |
| od->octo_mackey_set = 0; |
| } |
| if (!od->octo_mackey_set) { |
| octo_calc_hash(maccrd->crd_alg == CRYPTO_MD5_HMAC ? 0 : 1, |
| maccrd->crd_key, od->octo_hminner, od->octo_hmouter); |
| od->octo_mackey_set = 1; |
| } |
| } |
| |
| |
| if (!enccrd || (enccrd->crd_flags & CRD_F_ENCRYPT)) |
| (*od->octo_encrypt)(od, sg, sg_len, |
| auth_off, auth_len, crypt_off, crypt_len, icv_off, ivp); |
| else |
| (*od->octo_decrypt)(od, sg, sg_len, |
| auth_off, auth_len, crypt_off, crypt_len, icv_off, ivp); |
| |
| done: |
| crypto_done(crp); |
| return 0; |
| } |
| |
| static int |
| cryptocteon_init(void) |
| { |
| dprintk("%s(%p)\n", __FUNCTION__, cryptocteon_init); |
| |
| softc_device_init(&octo_softc, "cryptocteon", 0, octo_methods); |
| |
| octo_id = crypto_get_driverid(softc_get_device(&octo_softc), |
| CRYPTOCAP_F_HARDWARE | CRYPTOCAP_F_SYNC); |
| if (octo_id < 0) { |
| printk("Cryptocteon device cannot initialize!"); |
| return -ENODEV; |
| } |
| |
| crypto_register(octo_id, CRYPTO_MD5_HMAC, 0,0); |
| crypto_register(octo_id, CRYPTO_SHA1_HMAC, 0,0); |
| //crypto_register(octo_id, CRYPTO_MD5, 0,0); |
| //crypto_register(octo_id, CRYPTO_SHA1, 0,0); |
| crypto_register(octo_id, CRYPTO_DES_CBC, 0,0); |
| crypto_register(octo_id, CRYPTO_3DES_CBC, 0,0); |
| crypto_register(octo_id, CRYPTO_AES_CBC, 0,0); |
| |
| return(0); |
| } |
| |
| static void |
| cryptocteon_exit(void) |
| { |
| dprintk("%s()\n", __FUNCTION__); |
| crypto_unregister_all(octo_id); |
| octo_id = -1; |
| } |
| |
| module_init(cryptocteon_init); |
| module_exit(cryptocteon_exit); |
| |
| MODULE_LICENSE("BSD"); |
| MODULE_AUTHOR("David McCullough <david_mccullough@mcafee.com>"); |
| MODULE_DESCRIPTION("Cryptocteon (OCF module for Cavium OCTEON crypto)"); |