blob: 000cc8cca8b4b7f24b77b70446e1259675f4afe3 [file] [log] [blame]
/*******************************************************************************
Copyright (C) Marvell International Ltd. and its affiliates
This software file (the "File") is owned and distributed by Marvell
International Ltd. and/or its affiliates ("Marvell") under the following
alternative licensing terms. Once you have made an election to distribute the
File under one of the following license alternatives, please (i) delete this
introductory statement regarding license alternatives, (ii) delete the two
license alternatives that you have not elected to use and (iii) preserve the
Marvell copyright notice above.
********************************************************************************
Marvell GPL License Option
If you received this File from Marvell, you may opt to use, redistribute and/or
modify this File in accordance with the terms and conditions of the General
Public License Version 2, June 1991 (the "GPL License"), a copy of which is
available along with the File in the license.txt file or by writing to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 or
on the worldwide web at http://www.gnu.org/licenses/gpl.txt.
THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE IMPLIED
WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY
DISCLAIMED. The GPL License provides additional details about this warranty
disclaimer.
*******************************************************************************/
#ifndef AUTOCONF_INCLUDED
#include <linux/config.h>
#endif
#include <linux/interrupt.h>
#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 <asm/scatterlist.h>
#include <linux/spinlock.h>
#include "mvCommon.h"
#include "mvOs.h"
#include "ctrlEnv/mvCtrlEnvLib.h"
#include "cesa/mvCesaIf.h" /* moved here before cryptodev.h due to include dependencies */
#include "mvSysCesaApi.h"
#include <cryptodev.h>
#include <uio.h>
#include "mvDebug.h"
#include "cesa/mvMD5.h"
#include "cesa/mvSHA1.h"
#include "cesa/mvCesaRegs.h"
#include "cesa/AES/mvAes.h"
#include "cesa/mvLru.h"
#undef RT_DEBUG
#ifdef RT_DEBUG
static int debug = 1;
module_param(debug, int, 1);
MODULE_PARM_DESC(debug, "Enable debug");
#undef dprintk
#define dprintk(a...) if (debug) { printk(a); } else
#else
static int debug = 0;
#undef dprintk
#define dprintk(a...)
#endif
/* interrupt handling */
#undef CESA_OCF_TASKLET
extern int cesaReqResources[MV_CESA_CHANNELS];
/* support for spliting action into 2 actions */
#define CESA_OCF_SPLIT
/* general defines */
#define CESA_OCF_MAX_SES 128
#define CESA_Q_SIZE 64
#define CESA_RESULT_Q_SIZE 1024
/* data structures */
struct cesa_ocf_data {
int cipher_alg;
int auth_alg;
int encrypt_tn_auth;
#define auth_tn_decrypt encrypt_tn_auth
int ivlen;
int digestlen;
short sid_encrypt;
short sid_decrypt;
/* fragment workaround sessions */
short frag_wa_encrypt;
short frag_wa_decrypt;
short frag_wa_auth;
};
#define DIGEST_BUF_SIZE 32
struct cesa_ocf_process {
MV_CESA_COMMAND cesa_cmd;
MV_CESA_MBUF cesa_mbuf;
MV_BUF_INFO cesa_bufs[MV_CESA_MAX_MBUF_FRAGS];
char digest[DIGEST_BUF_SIZE];
int digest_len;
struct cryptop *crp;
int need_cb;
int valid;
};
/* global variables */
static int32_t cesa_ocf_id = -1;
static struct cesa_ocf_data **cesa_ocf_sessions = NULL;
static u_int32_t cesa_ocf_sesnum = 0;
static DEFINE_SPINLOCK(cesa_lock);
static atomic_t res_count;
static struct cesa_ocf_process *res_array[CESA_RESULT_Q_SIZE];
unsigned int res_empty;
unsigned int res_ready;
/* static APIs */
static int cesa_ocf_process (device_t, struct cryptop *, int);
static int cesa_ocf_newsession (device_t, u_int32_t *, struct cryptoini *);
static int cesa_ocf_freesession (device_t, u_int64_t);
static inline void cesa_callback (unsigned long);
static irqreturn_t cesa_interrupt_handler (int, void *);
static int cesa_ocf_init (void);
static void cesa_ocf_exit (void);
#ifdef CESA_OCF_TASKLET
static struct tasklet_struct cesa_ocf_tasklet;
#endif
static struct timeval tt_start;
static struct timeval tt_end;
static struct cesa_ocf_process *cesa_ocf_pool = NULL;
static int proc_empty;
/*
* dummy device structure
*/
static struct {
softc_device_decl sc_dev;
} mv_cesa_dev;
static device_method_t mv_cesa_methods = {
/* crypto device methods */
DEVMETHOD(cryptodev_newsession, cesa_ocf_newsession),
DEVMETHOD(cryptodev_freesession,cesa_ocf_freesession),
DEVMETHOD(cryptodev_process, cesa_ocf_process),
DEVMETHOD(cryptodev_kprocess, NULL),
};
unsigned int
get_usec(unsigned int start)
{
if(start) {
do_gettimeofday (&tt_start);
return 0;
}
else {
do_gettimeofday (&tt_end);
tt_end.tv_sec -= tt_start.tv_sec;
tt_end.tv_usec -= tt_start.tv_usec;
if (tt_end.tv_usec < 0) {
tt_end.tv_usec += 1000 * 1000;
tt_end.tv_sec -= 1;
}
}
printk("time taken is %d\n", (unsigned int)(tt_end.tv_usec + tt_end.tv_sec * 1000000));
return (tt_end.tv_usec + tt_end.tv_sec * 1000000);
}
static void
skb_copy_bits_back(struct sk_buff *skb, int offset, caddr_t cp, int len)
{
int i;
if (offset < skb_headlen(skb)) {
memcpy(skb->data + offset, cp, min_t(int, skb_headlen(skb), len));
len -= skb_headlen(skb);
cp += skb_headlen(skb);
}
offset -= skb_headlen(skb);
for (i = 0; len > 0 && i < skb_shinfo(skb)->nr_frags; i++) {
if (offset < skb_shinfo(skb)->frags[i].size) {
memcpy(page_address(skb_shinfo(skb)->frags[i].page) +
skb_shinfo(skb)->frags[i].page_offset,
cp, min_t(int, skb_shinfo(skb)->frags[i].size, len));
len -= skb_shinfo(skb)->frags[i].size;
cp += skb_shinfo(skb)->frags[i].size;
}
offset -= skb_shinfo(skb)->frags[i].size;
}
}
#ifdef RT_DEBUG
/*
* check that the crp action match the current session
*/
static int
ocf_check_action(struct cryptop *crp, struct cesa_ocf_data *cesa_ocf_cur_ses) {
int count = 0;
int encrypt = 0, decrypt = 0, auth = 0;
struct cryptodesc *crd;
/* Go through crypto descriptors, processing as we go */
for (crd = crp->crp_desc; crd; crd = crd->crd_next, count++) {
if(count > 2) {
printk("%s,%d: session mode is not supported.\n", __FILE__, __LINE__);
return 1;
}
/* Encryption /Decryption */
if(crd->crd_alg == cesa_ocf_cur_ses->cipher_alg) {
/* check that the action is compatible with session */
if(encrypt || decrypt) {
printk("%s,%d: session mode is not supported.\n", __FILE__, __LINE__);
return 1;
}
if(crd->crd_flags & CRD_F_ENCRYPT) { /* encrypt */
if( (count == 2) && (cesa_ocf_cur_ses->encrypt_tn_auth) ) {
printk("%s,%d: sequence isn't supported by this session.\n", __FILE__, __LINE__);
return 1;
}
encrypt++;
}
else { /* decrypt */
if( (count == 2) && !(cesa_ocf_cur_ses->auth_tn_decrypt) ) {
printk("%s,%d: sequence isn't supported by this session.\n", __FILE__, __LINE__);
return 1;
}
decrypt++;
}
}
/* Authentication */
else if(crd->crd_alg == cesa_ocf_cur_ses->auth_alg) {
/* check that the action is compatible with session */
if(auth) {
printk("%s,%d: session mode is not supported.\n", __FILE__, __LINE__);
return 1;
}
if( (count == 2) && (decrypt) && (cesa_ocf_cur_ses->auth_tn_decrypt)) {
printk("%s,%d: sequence isn't supported by this session.\n", __FILE__, __LINE__);
return 1;
}
if( (count == 2) && (encrypt) && !(cesa_ocf_cur_ses->encrypt_tn_auth)) {
printk("%s,%d: sequence isn't supported by this session.\n", __FILE__, __LINE__);
return 1;
}
auth++;
}
else {
printk("%s,%d: Alg isn't supported by this session.\n", __FILE__, __LINE__);
return 1;
}
}
return 0;
}
#endif
static inline struct cesa_ocf_process* cesa_ocf_alloc(void)
{
int proc = 0;
unsigned long flags;
spin_lock_irqsave(&cesa_lock, flags);
if(cesa_ocf_pool[proc_empty].valid != 0) {
spin_unlock_irqrestore(&cesa_lock, flags);
printk("%s,%d: Error, entry is overrided in cesa_ocf_pool(%d)\n", __FILE__, __LINE__,proc_empty);
return NULL;
}
cesa_ocf_pool[proc_empty].valid = 1;
proc = proc_empty;
proc_empty = ((proc_empty+1) % (CESA_Q_SIZE * MV_CESA_CHANNELS * 2));
spin_unlock_irqrestore(&cesa_lock, flags);
return &cesa_ocf_pool[proc];
}
static inline void cesa_ocf_free(struct cesa_ocf_process *ocf_process_p)
{
unsigned long flags;
spin_lock_irqsave(&cesa_lock, flags);
ocf_process_p->valid = 0;
spin_unlock_irqrestore(&cesa_lock, flags);
}
/*
* Process a request.
*/
static int
cesa_ocf_process(device_t dev, struct cryptop *crp, int hint)
{
struct cesa_ocf_process *cesa_ocf_cmd = NULL;
struct cesa_ocf_process *cesa_ocf_cmd_wa = NULL;
MV_CESA_COMMAND *cesa_cmd;
struct cryptodesc *crd;
struct cesa_ocf_data *cesa_ocf_cur_ses;
int sid = 0, temp_len = 0, i;
int encrypt = 0, decrypt = 0, auth = 0;
int status, free_resrc = 0;
struct sk_buff *skb = NULL;
struct uio *uiop = NULL;
unsigned char *ivp;
MV_BUF_INFO *p_buf_info;
MV_CESA_MBUF *p_mbuf_info;
unsigned long flags;
unsigned char chan = 0;
dprintk("%s()\n", __func__);
for(chan = 0; chan < MV_CESA_CHANNELS; chan++)
free_resrc += cesaReqResources[chan];
if (free_resrc == 0) {
dprintk("%s,%d: ERESTART\n", __FILE__, __LINE__);
return ERESTART;
}
#ifdef RT_DEBUG
/* Sanity check */
if (crp == NULL) {
printk("%s,%d: EINVAL\n", __FILE__, __LINE__);
return EINVAL;
}
if (crp->crp_desc == NULL || crp->crp_buf == NULL ) {
printk("%s,%d: EINVAL\n", __FILE__, __LINE__);
crp->crp_etype = EINVAL;
return EINVAL;
}
sid = crp->crp_sid & 0xffffffff;
if ((sid >= cesa_ocf_sesnum) || (cesa_ocf_sessions[sid] == NULL)) {
crp->crp_etype = ENOENT;
printk("%s,%d: ENOENT session %d \n", __FILE__, __LINE__, sid);
return EINVAL;
}
#endif
sid = crp->crp_sid & 0xffffffff;
crp->crp_etype = 0;
cesa_ocf_cur_ses = cesa_ocf_sessions[sid];
#ifdef RT_DEBUG
if(ocf_check_action(crp, cesa_ocf_cur_ses)){
goto p_error;
}
#endif
/* Allocate new cesa process from local pool */
cesa_ocf_cmd = cesa_ocf_alloc();
if (cesa_ocf_cmd == NULL) {
printk("%s,%d: ENOBUFS \n", __FILE__, __LINE__);
goto p_error;
}
/* init cesa_process */
cesa_ocf_cmd->crp = crp;
/* always call callback */
cesa_ocf_cmd->need_cb = 1;
/* init cesa_cmd for usage of the HALs */
cesa_cmd = &cesa_ocf_cmd->cesa_cmd;
cesa_cmd->pReqPrv = (void *)cesa_ocf_cmd;
cesa_cmd->sessionId = cesa_ocf_cur_ses->sid_encrypt; /* defualt use encrypt */
/* prepare src buffer */
/* we send the entire buffer to the HAL, even if only part of it should be encrypt/auth. */
/* if not using seesions for both encrypt and auth, then it will be wiser to to copy only */
/* from skip to crd_len. */
p_buf_info = cesa_ocf_cmd->cesa_bufs;
p_mbuf_info = &cesa_ocf_cmd->cesa_mbuf;
p_buf_info += 2; /* save 2 first buffers for IV and digest -
we won't append them to the end since, they
might be places in an unaligned addresses. */
p_mbuf_info->pFrags = p_buf_info;
temp_len = 0;
/* handle SKB */
if (crp->crp_flags & CRYPTO_F_SKBUF) {
dprintk("%s,%d: handle SKB.\n", __FILE__, __LINE__);
skb = (struct sk_buff *) crp->crp_buf;
if (skb_shinfo(skb)->nr_frags >= (MV_CESA_MAX_MBUF_FRAGS - 1)) {
printk("%s,%d: %d nr_frags > MV_CESA_MAX_MBUF_FRAGS", __FILE__, __LINE__, skb_shinfo(skb)->nr_frags);
goto p_error;
}
p_mbuf_info->mbufSize = skb->len;
temp_len = skb->len;
/* first skb fragment */
p_buf_info->bufSize = skb_headlen(skb);
p_buf_info->bufVirtPtr = skb->data;
p_buf_info++;
/* now handle all other skb fragments */
for ( i = 0; i < skb_shinfo(skb)->nr_frags; i++ ) {
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
p_buf_info->bufSize = frag->size;
p_buf_info->bufVirtPtr = page_address(frag->page) + frag->page_offset;
p_buf_info++;
}
p_mbuf_info->numFrags = skb_shinfo(skb)->nr_frags + 1;
}
/* handle UIO */
else if(crp->crp_flags & CRYPTO_F_IOV) {
dprintk("%s,%d: handle UIO.\n", __FILE__, __LINE__);
uiop = (struct uio *) crp->crp_buf;
if (uiop->uio_iovcnt > (MV_CESA_MAX_MBUF_FRAGS - 1)) {
printk("%s,%d: %d uio_iovcnt > MV_CESA_MAX_MBUF_FRAGS \n", __FILE__, __LINE__, uiop->uio_iovcnt);
goto p_error;
}
p_mbuf_info->mbufSize = crp->crp_ilen;
p_mbuf_info->numFrags = uiop->uio_iovcnt;
for(i = 0; i < uiop->uio_iovcnt; i++) {
p_buf_info->bufVirtPtr = uiop->uio_iov[i].iov_base;
p_buf_info->bufSize = uiop->uio_iov[i].iov_len;
temp_len += p_buf_info->bufSize;
dprintk("%s,%d: buf %x-> addr %x, size %x \n"
, __FILE__, __LINE__, i, (unsigned int)p_buf_info->bufVirtPtr, p_buf_info->bufSize);
p_buf_info++;
}
}
/* handle CONTIG */
else {
dprintk("%s,%d: handle CONTIG.\n", __FILE__, __LINE__);
p_mbuf_info->numFrags = 1;
p_mbuf_info->mbufSize = crp->crp_ilen;
p_buf_info->bufVirtPtr = crp->crp_buf;
p_buf_info->bufSize = crp->crp_ilen;
temp_len = crp->crp_ilen;
p_buf_info++;
}
/* Support up to 64K why? cause! */
if(crp->crp_ilen > 64*1024) {
printk("%s,%d: buf too big %x \n", __FILE__, __LINE__, crp->crp_ilen);
goto p_error;
}
if( temp_len != crp->crp_ilen ) {
printk("%s,%d: warning size don't match.(%x %x) \n", __FILE__, __LINE__, temp_len, crp->crp_ilen);
}
cesa_cmd->pSrc = p_mbuf_info;
cesa_cmd->pDst = p_mbuf_info;
/* restore p_buf_info to point to first available buf */
p_buf_info = cesa_ocf_cmd->cesa_bufs;
p_buf_info += 1;
/* Go through crypto descriptors, processing as we go */
for (crd = crp->crp_desc; crd; crd = crd->crd_next) {
/* Encryption /Decryption */
if(crd->crd_alg == cesa_ocf_cur_ses->cipher_alg) {
dprintk("%s,%d: cipher", __FILE__, __LINE__);
cesa_cmd->cryptoOffset = crd->crd_skip;
cesa_cmd->cryptoLength = crd->crd_len;
if(crd->crd_flags & CRD_F_ENCRYPT) { /* encrypt */
dprintk(" encrypt \n");
encrypt++;
/* handle IV */
if (crd->crd_flags & CRD_F_IV_EXPLICIT) { /* IV from USER */
dprintk("%s,%d: IV from USER (offset %x) \n", __FILE__, __LINE__, crd->crd_inject);
cesa_cmd->ivFromUser = 1;
ivp = crd->crd_iv;
/*
* do we have to copy the IV back to the buffer ?
*/
if ((crd->crd_flags & CRD_F_IV_PRESENT) == 0) {
dprintk("%s,%d: copy the IV back to the buffer\n", __FILE__, __LINE__);
cesa_cmd->ivOffset = crd->crd_inject;
if (crp->crp_flags & CRYPTO_F_SKBUF)
skb_copy_bits_back(skb, crd->crd_inject, ivp, cesa_ocf_cur_ses->ivlen);
else if (crp->crp_flags & CRYPTO_F_IOV)
cuio_copyback(uiop,crd->crd_inject, cesa_ocf_cur_ses->ivlen,(caddr_t)ivp);
else
memcpy(crp->crp_buf + crd->crd_inject, ivp, cesa_ocf_cur_ses->ivlen);
}
else {
dprintk("%s,%d: don't copy the IV back to the buffer \n", __FILE__, __LINE__);
p_mbuf_info->numFrags++;
p_mbuf_info->mbufSize += cesa_ocf_cur_ses->ivlen;
p_mbuf_info->pFrags = p_buf_info;
p_buf_info->bufVirtPtr = ivp;
p_buf_info->bufSize = cesa_ocf_cur_ses->ivlen;
p_buf_info--;
/* offsets */
cesa_cmd->ivOffset = 0;
cesa_cmd->cryptoOffset += cesa_ocf_cur_ses->ivlen;
if(auth) {
cesa_cmd->macOffset += cesa_ocf_cur_ses->ivlen;
cesa_cmd->digestOffset += cesa_ocf_cur_ses->ivlen;
}
}
}
else { /* random IV */
dprintk("%s,%d: random IV \n", __FILE__, __LINE__);
cesa_cmd->ivFromUser = 0;
/*
* do we have to copy the IV back to the buffer ?
*/
/* in this mode the HAL will always copy the IV */
/* given by the session to the ivOffset */
if ((crd->crd_flags & CRD_F_IV_PRESENT) == 0) {
cesa_cmd->ivOffset = crd->crd_inject;
}
else {
/* if IV isn't copy, then how will the user know which IV did we use??? */
printk("%s,%d: EINVAL\n", __FILE__, __LINE__);
goto p_error;
}
}
}
else { /* decrypt */
dprintk(" decrypt \n");
decrypt++;
cesa_cmd->sessionId = cesa_ocf_cur_ses->sid_decrypt;
/* handle IV */
if (crd->crd_flags & CRD_F_IV_EXPLICIT) {
dprintk("%s,%d: IV from USER \n", __FILE__, __LINE__);
/* append the IV buf to the mbuf */
cesa_cmd->ivFromUser = 1;
p_mbuf_info->numFrags++;
p_mbuf_info->mbufSize += cesa_ocf_cur_ses->ivlen;
p_mbuf_info->pFrags = p_buf_info;
p_buf_info->bufVirtPtr = crd->crd_iv;
p_buf_info->bufSize = cesa_ocf_cur_ses->ivlen;
p_buf_info--;
/* offsets */
cesa_cmd->ivOffset = 0;
cesa_cmd->cryptoOffset += cesa_ocf_cur_ses->ivlen;
if(auth) {
cesa_cmd->macOffset += cesa_ocf_cur_ses->ivlen;
cesa_cmd->digestOffset += cesa_ocf_cur_ses->ivlen;
}
}
else {
dprintk("%s,%d: IV inside the buffer \n", __FILE__, __LINE__);
cesa_cmd->ivFromUser = 0;
cesa_cmd->ivOffset = crd->crd_inject;
}
}
}
/* Authentication */
else if(crd->crd_alg == cesa_ocf_cur_ses->auth_alg) {
dprintk("%s,%d: Authentication \n", __FILE__, __LINE__);
auth++;
cesa_cmd->macOffset = crd->crd_skip;
cesa_cmd->macLength = crd->crd_len;
/* digest + mac */
cesa_cmd->digestOffset = crd->crd_inject;
}
else {
printk("%s,%d: Alg isn't supported by this session.\n", __FILE__, __LINE__);
goto p_error;
}
}
dprintk("\n");
dprintk("%s,%d: Sending Action: \n", __FILE__, __LINE__);
dprintk("%s,%d: IV from user: %d. IV offset %x \n", __FILE__, __LINE__, cesa_cmd->ivFromUser, cesa_cmd->ivOffset);
dprintk("%s,%d: crypt offset %x len %x \n", __FILE__, __LINE__, cesa_cmd->cryptoOffset, cesa_cmd->cryptoLength);
dprintk("%s,%d: Auth offset %x len %x \n", __FILE__, __LINE__, cesa_cmd->macOffset, cesa_cmd->macLength);
dprintk("%s,%d: set digest in offset %x . \n", __FILE__, __LINE__, cesa_cmd->digestOffset);
if(debug) {
mvCesaIfDebugMbuf("SRC BUFFER", cesa_cmd->pSrc, 0, cesa_cmd->pSrc->mbufSize);
}
cesa_cmd->split = MV_CESA_SPLIT_NONE;
/* send action to HAL */
spin_lock_irqsave(&cesa_lock, flags);
status = mvCesaIfAction(cesa_cmd);
spin_unlock_irqrestore(&cesa_lock, flags);
/* action not allowed */
if(status == MV_NOT_ALLOWED) {
#ifdef CESA_OCF_SPLIT
/* if both encrypt and auth try to split */
if(auth && (encrypt || decrypt)) {
MV_CESA_COMMAND *cesa_cmd_wa;
/* Allocate new cesa process from local pool and initialize it */
cesa_ocf_cmd_wa = cesa_ocf_alloc();
if (cesa_ocf_cmd_wa == NULL) {
printk("%s,%d: ENOBUFS \n", __FILE__, __LINE__);
goto p_error;
}
memcpy(cesa_ocf_cmd_wa, cesa_ocf_cmd, sizeof(struct cesa_ocf_process));
cesa_cmd_wa = &cesa_ocf_cmd_wa->cesa_cmd;
cesa_cmd_wa->pReqPrv = (void *)cesa_ocf_cmd_wa;
cesa_ocf_cmd_wa->need_cb = 0;
cesa_cmd_wa->split = MV_CESA_SPLIT_FIRST;
cesa_cmd->split = MV_CESA_SPLIT_SECOND;
/* break requests to two operation, first operation completion won't call callback */
if((decrypt) && (cesa_ocf_cur_ses->auth_tn_decrypt)) {
cesa_cmd_wa->sessionId = cesa_ocf_cur_ses->frag_wa_auth;
cesa_cmd->sessionId = cesa_ocf_cur_ses->frag_wa_decrypt;
}
else if((decrypt) && !(cesa_ocf_cur_ses->auth_tn_decrypt)) {
cesa_cmd_wa->sessionId = cesa_ocf_cur_ses->frag_wa_decrypt;
cesa_cmd->sessionId = cesa_ocf_cur_ses->frag_wa_auth;
}
else if((encrypt) && (cesa_ocf_cur_ses->encrypt_tn_auth)) {
cesa_cmd_wa->sessionId = cesa_ocf_cur_ses->frag_wa_encrypt;
cesa_cmd->sessionId = cesa_ocf_cur_ses->frag_wa_auth;
}
else if((encrypt) && !(cesa_ocf_cur_ses->encrypt_tn_auth)){
cesa_cmd_wa->sessionId = cesa_ocf_cur_ses->frag_wa_auth;
cesa_cmd->sessionId = cesa_ocf_cur_ses->frag_wa_encrypt;
}
else {
printk("%s,%d: Unsupporterd fragment wa mode \n", __FILE__, __LINE__);
goto p_error;
}
/* send the 2 actions to the HAL */
spin_lock_irqsave(&cesa_lock, flags);
status = mvCesaIfAction(cesa_cmd_wa);
spin_unlock_irqrestore(&cesa_lock, flags);
if((status != MV_NO_MORE) && (status != MV_OK)) {
printk("%s,%d: cesa action failed, status = 0x%x\n", __FILE__, __LINE__, status);
goto p_error;
}
spin_lock_irqsave(&cesa_lock, flags);
status = mvCesaIfAction(cesa_cmd);
spin_unlock_irqrestore(&cesa_lock, flags);
}
/* action not allowed and can't split */
else
#endif
{
goto p_error;
}
}
/* Hal Q is full, send again. This should never happen */
if(status == MV_NO_RESOURCE) {
printk("%s,%d: cesa no more resources \n", __FILE__, __LINE__);
if(cesa_ocf_cmd)
cesa_ocf_free(cesa_ocf_cmd);
if(cesa_ocf_cmd_wa)
cesa_ocf_free(cesa_ocf_cmd_wa);
return ERESTART;
}
else if((status != MV_NO_MORE) && (status != MV_OK)) {
printk("%s,%d: cesa action failed, status = 0x%x\n", __FILE__, __LINE__, status);
goto p_error;
}
return 0;
p_error:
crp->crp_etype = EINVAL;
if(cesa_ocf_cmd)
cesa_ocf_free(cesa_ocf_cmd);
if(cesa_ocf_cmd_wa)
cesa_ocf_free(cesa_ocf_cmd_wa);
return EINVAL;
}
/*
* cesa callback.
*/
static inline void
cesa_callback(unsigned long dummy)
{
struct cesa_ocf_process *cesa_ocf_cmd = NULL;
struct cryptop *crp = NULL;
dprintk("%s()\n", __func__);
while (atomic_read(&res_count) != 0) {
cesa_ocf_cmd = res_array[res_ready];
crp = cesa_ocf_cmd->crp;
if (cesa_ocf_cmd->need_cb) {
if (debug) {
mvCesaIfDebugMbuf("DST BUFFER", cesa_ocf_cmd->cesa_cmd.pDst, 0,
cesa_ocf_cmd->cesa_cmd.pDst->mbufSize);
}
crypto_done(crp);
}
cesa_ocf_free(cesa_ocf_cmd);
res_ready = ((res_ready + 1) % CESA_RESULT_Q_SIZE);
atomic_dec(&res_count);
}
return;
}
/*
* cesa Interrupt Service Routine.
*/
static irqreturn_t
cesa_interrupt_handler(int irq, void *arg)
{
MV_CESA_RESULT result;
MV_STATUS status;
u32 cause;
u8 chan;
dprintk("%s()\n", __func__);
for (chan = 0; chan < MV_CESA_CHANNELS; chan++) {
/* Read cause register */
cause = MV_REG_READ(MV_CESA_ISR_CAUSE_REG(chan));
if (likely((cause & MV_CESA_CAUSE_ACC_DMA_MASK) != 0)) {
/* clear interrupts */
MV_REG_WRITE(MV_CESA_ISR_CAUSE_REG(chan), 0);
while (1) {
if(likely(atomic_read(&res_count) < CESA_RESULT_Q_SIZE)) {
/* Get Ready requests */
spin_lock(&cesa_lock);
status = mvCesaIfReadyGet(chan, &result);
spin_unlock(&cesa_lock);
if(likely(status == MV_OK)) {
res_array[res_empty] = (struct cesa_ocf_process *)result.pReqPrv;
res_empty = ((res_empty + 1) % CESA_RESULT_Q_SIZE);
atomic_inc(&res_count);
continue;
} else
break;
} else {
/* In case reaching this point -res_array should be tuned */
printk("%s: Error: Q request is full(chan=%d)\n", __func__, chan);
return IRQ_HANDLED;
}
}
}
}
if(likely(atomic_read(&res_count) > 0))
#ifdef CESA_OCF_TASKLET
tasklet_hi_schedule(&cesa_ocf_tasklet);
#else
cesa_callback(0);
#endif
return IRQ_HANDLED;
}
/*
* Open a session.
*/
static int
/*cesa_ocf_newsession(void *arg, u_int32_t *sid, struct cryptoini *cri)*/
cesa_ocf_newsession(device_t dev, u_int32_t *sid, struct cryptoini *cri)
{
u32 status = 0, i = 0;
unsigned long flags = 0;
u32 count = 0, auth = 0, encrypt =0;
struct cesa_ocf_data *cesa_ocf_cur_ses;
MV_CESA_OPEN_SESSION cesa_session;
MV_CESA_OPEN_SESSION *cesa_ses = &cesa_session;
dprintk("%s()\n", __func__);
if (sid == NULL || cri == NULL) {
printk("%s,%d: EINVAL\n", __FILE__, __LINE__);
return EINVAL;
}
if (cesa_ocf_sessions) {
for (i = 1; i < cesa_ocf_sesnum; i++)
if (cesa_ocf_sessions[i] == NULL)
break;
} else
i = 1;
if (cesa_ocf_sessions == NULL || i == cesa_ocf_sesnum) {
struct cesa_ocf_data **cesa_ocf_new_sessions;
if (cesa_ocf_sessions == NULL) {
i = 1; /* We leave cesa_ocf_sessions[0] empty */
cesa_ocf_sesnum = CESA_OCF_MAX_SES;
}
else
cesa_ocf_sesnum *= 2;
cesa_ocf_new_sessions = kmalloc(cesa_ocf_sesnum * sizeof(struct cesa_ocf_data *), SLAB_ATOMIC);
if (cesa_ocf_new_sessions == NULL) {
/* Reset session number */
if (cesa_ocf_sesnum == CESA_OCF_MAX_SES)
cesa_ocf_sesnum = 0;
else
cesa_ocf_sesnum /= 2;
printk("%s,%d: ENOBUFS\n", __FILE__, __LINE__);
return ENOBUFS;
}
memset(cesa_ocf_new_sessions, 0, cesa_ocf_sesnum * sizeof(struct cesa_ocf_data *));
/* Copy existing sessions */
if (cesa_ocf_sessions) {
memcpy(cesa_ocf_new_sessions, cesa_ocf_sessions,
(cesa_ocf_sesnum / 2) * sizeof(struct cesa_ocf_data *));
kfree(cesa_ocf_sessions);
}
cesa_ocf_sessions = cesa_ocf_new_sessions;
}
cesa_ocf_sessions[i] = (struct cesa_ocf_data *) kmalloc(sizeof(struct cesa_ocf_data),
SLAB_ATOMIC);
if (cesa_ocf_sessions[i] == NULL) {
cesa_ocf_freesession(NULL, i);
dprintk("%s,%d: EINVAL\n", __FILE__, __LINE__);
return ENOBUFS;
}
dprintk("%s,%d: new session %d \n", __FILE__, __LINE__, i);
*sid = i;
cesa_ocf_cur_ses = cesa_ocf_sessions[i];
memset(cesa_ocf_cur_ses, 0, sizeof(struct cesa_ocf_data));
cesa_ocf_cur_ses->sid_encrypt = -1;
cesa_ocf_cur_ses->sid_decrypt = -1;
cesa_ocf_cur_ses->frag_wa_encrypt = -1;
cesa_ocf_cur_ses->frag_wa_decrypt = -1;
cesa_ocf_cur_ses->frag_wa_auth = -1;
/* init the session */
memset(cesa_ses, 0, sizeof(MV_CESA_OPEN_SESSION));
count = 1;
while (cri) {
if(count > 2) {
printk("%s,%d: don't support more then 2 operations\n", __FILE__, __LINE__);
goto error;
}
switch (cri->cri_alg) {
case CRYPTO_AES_CBC:
dprintk("%s,%d: (%d) AES CBC \n", __FILE__, __LINE__, count);
cesa_ocf_cur_ses->cipher_alg = cri->cri_alg;
cesa_ocf_cur_ses->ivlen = MV_CESA_AES_BLOCK_SIZE;
cesa_ses->cryptoAlgorithm = MV_CESA_CRYPTO_AES;
cesa_ses->cryptoMode = MV_CESA_CRYPTO_CBC;
if(cri->cri_klen/8 > MV_CESA_MAX_CRYPTO_KEY_LENGTH) {
printk("%s,%d: CRYPTO key too long.\n", __FILE__, __LINE__);
goto error;
}
memcpy(cesa_ses->cryptoKey, cri->cri_key, cri->cri_klen/8);
dprintk("%s,%d: key length %d \n", __FILE__, __LINE__, cri->cri_klen/8);
cesa_ses->cryptoKeyLength = cri->cri_klen/8;
encrypt += count;
break;
case CRYPTO_3DES_CBC:
dprintk("%s,%d: (%d) 3DES CBC \n", __FILE__, __LINE__, count);
cesa_ocf_cur_ses->cipher_alg = cri->cri_alg;
cesa_ocf_cur_ses->ivlen = MV_CESA_3DES_BLOCK_SIZE;
cesa_ses->cryptoAlgorithm = MV_CESA_CRYPTO_3DES;
cesa_ses->cryptoMode = MV_CESA_CRYPTO_CBC;
if(cri->cri_klen/8 > MV_CESA_MAX_CRYPTO_KEY_LENGTH) {
printk("%s,%d: CRYPTO key too long.\n", __FILE__, __LINE__);
goto error;
}
memcpy(cesa_ses->cryptoKey, cri->cri_key, cri->cri_klen/8);
cesa_ses->cryptoKeyLength = cri->cri_klen/8;
encrypt += count;
break;
case CRYPTO_DES_CBC:
dprintk("%s,%d: (%d) DES CBC \n", __FILE__, __LINE__, count);
cesa_ocf_cur_ses->cipher_alg = cri->cri_alg;
cesa_ocf_cur_ses->ivlen = MV_CESA_DES_BLOCK_SIZE;
cesa_ses->cryptoAlgorithm = MV_CESA_CRYPTO_DES;
cesa_ses->cryptoMode = MV_CESA_CRYPTO_CBC;
if(cri->cri_klen/8 > MV_CESA_MAX_CRYPTO_KEY_LENGTH) {
printk("%s,%d: CRYPTO key too long.\n", __FILE__, __LINE__);
goto error;
}
memcpy(cesa_ses->cryptoKey, cri->cri_key, cri->cri_klen/8);
cesa_ses->cryptoKeyLength = cri->cri_klen/8;
encrypt += count;
break;
case CRYPTO_MD5:
case CRYPTO_MD5_HMAC:
dprintk("%s,%d: (%d) %sMD5 CBC \n", __FILE__, __LINE__, count, (cri->cri_alg != CRYPTO_MD5)? "H-":" ");
cesa_ocf_cur_ses->auth_alg = cri->cri_alg;
cesa_ocf_cur_ses->digestlen = (cri->cri_alg == CRYPTO_MD5)? MV_CESA_MD5_DIGEST_SIZE : 12;
cesa_ses->macMode = (cri->cri_alg == CRYPTO_MD5)? MV_CESA_MAC_MD5 : MV_CESA_MAC_HMAC_MD5;
if(cri->cri_klen/8 > MV_CESA_MAX_CRYPTO_KEY_LENGTH) {
printk("%s,%d: MAC key too long. \n", __FILE__, __LINE__);
goto error;
}
cesa_ses->macKeyLength = cri->cri_klen/8;
memcpy(cesa_ses->macKey, cri->cri_key, cri->cri_klen/8);
cesa_ses->digestSize = cesa_ocf_cur_ses->digestlen;
auth += count;
break;
case CRYPTO_SHA1:
case CRYPTO_SHA1_HMAC:
dprintk("%s,%d: (%d) %sSHA1 CBC \n", __FILE__, __LINE__, count, (cri->cri_alg != CRYPTO_SHA1)? "H-":" ");
cesa_ocf_cur_ses->auth_alg = cri->cri_alg;
cesa_ocf_cur_ses->digestlen = (cri->cri_alg == CRYPTO_SHA1)? MV_CESA_SHA1_DIGEST_SIZE : 12;
cesa_ses->macMode = (cri->cri_alg == CRYPTO_SHA1)? MV_CESA_MAC_SHA1 : MV_CESA_MAC_HMAC_SHA1;
if(cri->cri_klen/8 > MV_CESA_MAX_CRYPTO_KEY_LENGTH) {
printk("%s,%d: MAC key too long. \n", __FILE__, __LINE__);
goto error;
}
cesa_ses->macKeyLength = cri->cri_klen/8;
memcpy(cesa_ses->macKey, cri->cri_key, cri->cri_klen/8);
cesa_ses->digestSize = cesa_ocf_cur_ses->digestlen;
auth += count;
break;
default:
printk("%s,%d: unknown algo 0x%x\n", __FILE__, __LINE__, cri->cri_alg);
goto error;
}
cri = cri->cri_next;
count++;
}
if((encrypt > 2) || (auth > 2)) {
printk("%s,%d: session mode is not supported.\n", __FILE__, __LINE__);
goto error;
}
/* create new sessions in HAL */
if(encrypt) {
cesa_ses->operation = MV_CESA_CRYPTO_ONLY;
/* encrypt session */
if(auth == 1) {
cesa_ses->operation = MV_CESA_MAC_THEN_CRYPTO;
}
else if(auth == 2) {
cesa_ses->operation = MV_CESA_CRYPTO_THEN_MAC;
cesa_ocf_cur_ses->encrypt_tn_auth = 1;
}
else {
cesa_ses->operation = MV_CESA_CRYPTO_ONLY;
}
cesa_ses->direction = MV_CESA_DIR_ENCODE;
spin_lock_irqsave(&cesa_lock, flags);
status = mvCesaIfSessionOpen(cesa_ses, &cesa_ocf_cur_ses->sid_encrypt);
spin_unlock_irqrestore(&cesa_lock, flags);
if(status != MV_OK) {
printk("%s,%d: Can't open new session - status = 0x%x\n", __FILE__, __LINE__, status);
goto error;
}
/* decrypt session */
if( cesa_ses->operation == MV_CESA_MAC_THEN_CRYPTO ) {
cesa_ses->operation = MV_CESA_CRYPTO_THEN_MAC;
}
else if( cesa_ses->operation == MV_CESA_CRYPTO_THEN_MAC ) {
cesa_ses->operation = MV_CESA_MAC_THEN_CRYPTO;
}
cesa_ses->direction = MV_CESA_DIR_DECODE;
spin_lock_irqsave(&cesa_lock, flags);
status = mvCesaIfSessionOpen(cesa_ses, &cesa_ocf_cur_ses->sid_decrypt);
spin_unlock_irqrestore(&cesa_lock, flags);
if(status != MV_OK) {
printk("%s,%d: Can't open new session - status = 0x%x\n", __FILE__, __LINE__, status);
goto error;
}
/* preapre one action sessions for case we will need to split an action */
#ifdef CESA_OCF_SPLIT
if(( cesa_ses->operation == MV_CESA_MAC_THEN_CRYPTO ) ||
( cesa_ses->operation == MV_CESA_CRYPTO_THEN_MAC )) {
/* open one session for encode and one for decode */
cesa_ses->operation = MV_CESA_CRYPTO_ONLY;
cesa_ses->direction = MV_CESA_DIR_ENCODE;
spin_lock_irqsave(&cesa_lock, flags);
status = mvCesaIfSessionOpen(cesa_ses, &cesa_ocf_cur_ses->frag_wa_encrypt);
spin_unlock_irqrestore(&cesa_lock, flags);
if(status != MV_OK) {
printk("%s,%d: Can't open new session - status = 0x%x\n", __FILE__, __LINE__, status);
goto error;
}
cesa_ses->direction = MV_CESA_DIR_DECODE;
spin_lock_irqsave(&cesa_lock, flags);
status = mvCesaIfSessionOpen(cesa_ses, &cesa_ocf_cur_ses->frag_wa_decrypt);
spin_unlock_irqrestore(&cesa_lock, flags);
if(status != MV_OK) {
printk("%s,%d: Can't open new session - status = 0x%x\n", __FILE__, __LINE__, status);
goto error;
}
/* open one session for auth */
cesa_ses->operation = MV_CESA_MAC_ONLY;
cesa_ses->direction = MV_CESA_DIR_ENCODE;
spin_lock_irqsave(&cesa_lock, flags);
status = mvCesaIfSessionOpen(cesa_ses, &cesa_ocf_cur_ses->frag_wa_auth);
spin_unlock_irqrestore(&cesa_lock, flags);
if(status != MV_OK) {
printk("%s,%d: Can't open new session - status = 0x%x\n", __FILE__, __LINE__, status);
goto error;
}
}
#endif
}
else { /* only auth */
cesa_ses->operation = MV_CESA_MAC_ONLY;
cesa_ses->direction = MV_CESA_DIR_ENCODE;
spin_lock_irqsave(&cesa_lock, flags);
status = mvCesaIfSessionOpen(cesa_ses, &cesa_ocf_cur_ses->sid_encrypt);
spin_unlock_irqrestore(&cesa_lock, flags);
if(status != MV_OK) {
printk("%s,%d: Can't open new session - status = 0x%x\n", __FILE__, __LINE__, status);
goto error;
}
}
return 0;
error:
cesa_ocf_freesession(NULL, *sid);
return EINVAL;
}
/*
* Free a session.
*/
static int
cesa_ocf_freesession(device_t dev, u_int64_t tid)
{
struct cesa_ocf_data *cesa_ocf_cur_ses;
u_int32_t sid = CRYPTO_SESID2LID(tid);
unsigned long flags = 0;
dprintk("%s() %d \n", __func__, sid);
if (sid > cesa_ocf_sesnum || cesa_ocf_sessions == NULL ||
cesa_ocf_sessions[sid] == NULL) {
dprintk("%s,%d: EINVAL\n", __FILE__, __LINE__);
return EINVAL;
}
/* Silently accept and return */
if (sid == 0)
return(0);
/* release session from HAL */
cesa_ocf_cur_ses = cesa_ocf_sessions[sid];
if (cesa_ocf_cur_ses->sid_encrypt != -1) {
spin_lock_irqsave(&cesa_lock, flags);
mvCesaIfSessionClose(cesa_ocf_cur_ses->sid_encrypt);
spin_unlock_irqrestore(&cesa_lock, flags);
}
if (cesa_ocf_cur_ses->sid_decrypt != -1) {
spin_lock_irqsave(&cesa_lock, flags);
mvCesaIfSessionClose(cesa_ocf_cur_ses->sid_decrypt);
spin_unlock_irqrestore(&cesa_lock, flags);
}
if (cesa_ocf_cur_ses->frag_wa_encrypt != -1) {
spin_lock_irqsave(&cesa_lock, flags);
mvCesaIfSessionClose(cesa_ocf_cur_ses->frag_wa_encrypt);
spin_unlock_irqrestore(&cesa_lock, flags);
}
if (cesa_ocf_cur_ses->frag_wa_decrypt != -1) {
spin_lock_irqsave(&cesa_lock, flags);
mvCesaIfSessionClose(cesa_ocf_cur_ses->frag_wa_decrypt);
spin_unlock_irqrestore(&cesa_lock, flags);
}
if (cesa_ocf_cur_ses->frag_wa_auth != -1) {
spin_lock_irqsave(&cesa_lock, flags);
mvCesaIfSessionClose(cesa_ocf_cur_ses->frag_wa_auth);
spin_unlock_irqrestore(&cesa_lock, flags);
}
kfree(cesa_ocf_cur_ses);
cesa_ocf_sessions[sid] = NULL;
return 0;
}
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30))
extern int crypto_init(void);
#endif
/*
* our driver startup and shutdown routines
*/
static int
cesa_ocf_init(void)
{
u8 chan = 0;
const char *irq_str[] = {"cesa0","cesa1"};
if (mvCtrlPwrClckGet(CESA_UNIT_ID, 0) == MV_FALSE)
return 0;
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30))
crypto_init();
#endif
#if defined(CONFIG_MV78200) || defined(CONFIG_MV632X)
if (MV_FALSE == mvSocUnitIsMappedToThisCpu(CESA))
{
dprintk("CESA is not mapped to this CPU\n");
return -ENODEV;
}
#endif
dprintk("%s\n", __func__);
res_empty = 0;
res_ready = 0;
/* The pool size here is twice the requests queue size due to reordering */
cesa_ocf_pool = (struct cesa_ocf_process*)kmalloc((sizeof(struct cesa_ocf_process) *
CESA_Q_SIZE * MV_CESA_CHANNELS * 2), GFP_ATOMIC);
if (cesa_ocf_pool == NULL) {
printk("%s,%d: ENOBUFS \n", __FILE__, __LINE__);
return EINVAL;
}
proc_empty = 0;
memset(cesa_ocf_pool, 0, (sizeof(struct cesa_ocf_process) * CESA_Q_SIZE * MV_CESA_CHANNELS * 2));
memset(&mv_cesa_dev, 0, sizeof(mv_cesa_dev));
softc_device_init(&mv_cesa_dev, "MV CESA", 0, mv_cesa_methods);
cesa_ocf_id = crypto_get_driverid(softc_get_device(&mv_cesa_dev),CRYPTOCAP_F_HARDWARE);
if (cesa_ocf_id < 0)
panic("MV CESA crypto device cannot initialize!");
dprintk("%s,%d: cesa ocf device id is %d \n", __FILE__, __LINE__, cesa_ocf_id);
/* CESA unit is auto power on off */
#if 0
if (MV_FALSE == mvCtrlPwrClckGet(CESA_UNIT_ID,0))
{
printk("\nWarning CESA %d is Powered Off\n",0);
return EINVAL;
}
#endif
if( MV_OK != mvSysCesaInit(CESA_OCF_MAX_SES*5, CESA_Q_SIZE, NULL) ) {
printk("%s,%d: mvCesaInit Failed. \n", __FILE__, __LINE__);
return EINVAL;
}
for(chan = 0; chan < MV_CESA_CHANNELS; chan++) {
/* clear and unmask Int */
MV_REG_WRITE( MV_CESA_ISR_CAUSE_REG(chan), 0);
MV_REG_WRITE( MV_CESA_ISR_MASK_REG(chan), MV_CESA_CAUSE_ACC_DMA_MASK);
/* register interrupt */
if( request_irq( CESA_IRQ(chan), cesa_interrupt_handler,
(IRQF_DISABLED) , irq_str[chan], &cesa_ocf_id) < 0) {
printk("%s,%d: cannot assign irq %x\n", __FILE__, __LINE__, CESA_IRQ(chan));
return EINVAL;
}
}
#ifdef CESA_OCF_TASKLET
tasklet_init(&cesa_ocf_tasklet, cesa_callback, (unsigned int) 0);
#endif
#define REGISTER(alg) \
crypto_register(cesa_ocf_id, alg, 0,0)
REGISTER(CRYPTO_AES_CBC);
REGISTER(CRYPTO_DES_CBC);
REGISTER(CRYPTO_3DES_CBC);
REGISTER(CRYPTO_MD5);
REGISTER(CRYPTO_MD5_HMAC);
REGISTER(CRYPTO_SHA1);
REGISTER(CRYPTO_SHA1_HMAC);
#undef REGISTER
return 0;
}
static void
cesa_ocf_exit(void)
{
u8 chan = 0;
dprintk("%s()\n", __func__);
crypto_unregister_all(cesa_ocf_id);
cesa_ocf_id = -1;
kfree(cesa_ocf_pool);
for(chan = 0; chan < MV_CESA_CHANNELS; chan++) {
free_irq(CESA_IRQ(chan), NULL);
/* mask and clear Int */
MV_REG_WRITE( MV_CESA_ISR_MASK_REG(chan), 0);
MV_REG_WRITE( MV_CESA_ISR_CAUSE_REG(chan), 0);
}
if( MV_OK != mvCesaIfFinish() ) {
printk("%s,%d: mvCesaFinish Failed. \n", __FILE__, __LINE__);
return;
}
}
module_init(cesa_ocf_init);
module_exit(cesa_ocf_exit);
MODULE_LICENSE("Marvell/GPL");
MODULE_AUTHOR("Ronen Shitrit");
MODULE_DESCRIPTION("OCF module for Marvell CESA based SoC");