diff options
Diffstat (limited to 'arch/cris/arch-v32/drivers/cryptocop.c')
-rw-r--r-- | arch/cris/arch-v32/drivers/cryptocop.c | 3522 |
1 files changed, 3522 insertions, 0 deletions
diff --git a/arch/cris/arch-v32/drivers/cryptocop.c b/arch/cris/arch-v32/drivers/cryptocop.c new file mode 100644 index 00000000000..ca72076c630 --- /dev/null +++ b/arch/cris/arch-v32/drivers/cryptocop.c @@ -0,0 +1,3522 @@ +/* $Id: cryptocop.c,v 1.13 2005/04/21 17:27:55 henriken Exp $ + * + * Stream co-processor driver for the ETRAX FS + * + * Copyright (C) 2003-2005 Axis Communications AB + */ + +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/spinlock.h> +#include <linux/stddef.h> + +#include <asm/uaccess.h> +#include <asm/io.h> +#include <asm/atomic.h> + +#include <linux/list.h> +#include <linux/interrupt.h> + +#include <asm/signal.h> +#include <asm/irq.h> + +#include <asm/arch/dma.h> +#include <asm/arch/hwregs/dma.h> +#include <asm/arch/hwregs/reg_map.h> +#include <asm/arch/hwregs/reg_rdwr.h> +#include <asm/arch/hwregs/intr_vect_defs.h> + +#include <asm/arch/hwregs/strcop.h> +#include <asm/arch/hwregs/strcop_defs.h> +#include <asm/arch/cryptocop.h> + + + +#define DESCR_ALLOC_PAD (31) + +struct cryptocop_dma_desc { + char *free_buf; /* If non-null will be kfreed in free_cdesc() */ + dma_descr_data *dma_descr; + + unsigned char dma_descr_buf[sizeof(dma_descr_data) + DESCR_ALLOC_PAD]; + + unsigned int from_pool:1; /* If 1 'allocated' from the descriptor pool. */ + struct cryptocop_dma_desc *next; +}; + + +struct cryptocop_int_operation{ + void *alloc_ptr; + cryptocop_session_id sid; + + dma_descr_context ctx_out; + dma_descr_context ctx_in; + + /* DMA descriptors allocated by driver. */ + struct cryptocop_dma_desc *cdesc_out; + struct cryptocop_dma_desc *cdesc_in; + + /* Strcop config to use. */ + cryptocop_3des_mode tdes_mode; + cryptocop_csum_type csum_mode; + + /* DMA descrs provided by consumer. */ + dma_descr_data *ddesc_out; + dma_descr_data *ddesc_in; +}; + + +struct cryptocop_tfrm_ctx { + cryptocop_tfrm_id tid; + unsigned int blocklength; + + unsigned int start_ix; + + struct cryptocop_tfrm_cfg *tcfg; + struct cryptocop_transform_ctx *tctx; + + unsigned char previous_src; + unsigned char current_src; + + /* Values to use in metadata out. */ + unsigned char hash_conf; + unsigned char hash_mode; + unsigned char ciph_conf; + unsigned char cbcmode; + unsigned char decrypt; + + unsigned int requires_padding:1; + unsigned int strict_block_length:1; + unsigned int active:1; + unsigned int done:1; + size_t consumed; + size_t produced; + + /* Pad (input) descriptors to put in the DMA out list when the transform + * output is put on the DMA in list. */ + struct cryptocop_dma_desc *pad_descs; + + struct cryptocop_tfrm_ctx *prev_src; + struct cryptocop_tfrm_ctx *curr_src; + + /* Mapping to HW. */ + unsigned char unit_no; +}; + + +struct cryptocop_private{ + cryptocop_session_id sid; + struct cryptocop_private *next; +}; + +/* Session list. */ + +struct cryptocop_transform_ctx{ + struct cryptocop_transform_init init; + unsigned char dec_key[CRYPTOCOP_MAX_KEY_LENGTH]; + unsigned int dec_key_set:1; + + struct cryptocop_transform_ctx *next; +}; + + +struct cryptocop_session{ + cryptocop_session_id sid; + + struct cryptocop_transform_ctx *tfrm_ctx; + + struct cryptocop_session *next; +}; + +/* Priority levels for jobs sent to the cryptocop. Checksum operations from + kernel have highest priority since TCPIP stack processing must not + be a bottleneck. */ +typedef enum { + cryptocop_prio_kernel_csum = 0, + cryptocop_prio_kernel = 1, + cryptocop_prio_user = 2, + cryptocop_prio_no_prios = 3 +} cryptocop_queue_priority; + +struct cryptocop_prio_queue{ + struct list_head jobs; + cryptocop_queue_priority prio; +}; + +struct cryptocop_prio_job{ + struct list_head node; + cryptocop_queue_priority prio; + + struct cryptocop_operation *oper; + struct cryptocop_int_operation *iop; +}; + +struct ioctl_job_cb_ctx { + unsigned int processed:1; +}; + + +static struct cryptocop_session *cryptocop_sessions = NULL; +spinlock_t cryptocop_sessions_lock; + +/* Next Session ID to assign. */ +static cryptocop_session_id next_sid = 1; + +/* Pad for checksum. */ +static const char csum_zero_pad[1] = {0x00}; + +/* Trash buffer for mem2mem operations. */ +#define MEM2MEM_DISCARD_BUF_LENGTH (512) +static unsigned char mem2mem_discard_buf[MEM2MEM_DISCARD_BUF_LENGTH]; + +/* Descriptor pool. */ +/* FIXME Tweak this value. */ +#define CRYPTOCOP_DESCRIPTOR_POOL_SIZE (100) +static struct cryptocop_dma_desc descr_pool[CRYPTOCOP_DESCRIPTOR_POOL_SIZE]; +static struct cryptocop_dma_desc *descr_pool_free_list; +static int descr_pool_no_free; +static spinlock_t descr_pool_lock; + +/* Lock to stop cryptocop to start processing of a new operation. The holder + of this lock MUST call cryptocop_start_job() after it is unlocked. */ +spinlock_t cryptocop_process_lock; + +static struct cryptocop_prio_queue cryptocop_job_queues[cryptocop_prio_no_prios]; +static spinlock_t cryptocop_job_queue_lock; +static struct cryptocop_prio_job *cryptocop_running_job = NULL; +static spinlock_t running_job_lock; + +/* The interrupt handler appends completed jobs to this list. The scehduled + * tasklet removes them upon sending the response to the crypto consumer. */ +static struct list_head cryptocop_completed_jobs; +static spinlock_t cryptocop_completed_jobs_lock; + +DECLARE_WAIT_QUEUE_HEAD(cryptocop_ioc_process_wq); + + +/** Local functions. **/ + +static int cryptocop_open(struct inode *, struct file *); + +static int cryptocop_release(struct inode *, struct file *); + +static int cryptocop_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); + +static void cryptocop_start_job(void); + +static int cryptocop_job_queue_insert(cryptocop_queue_priority prio, struct cryptocop_operation *operation); +static int cryptocop_job_setup(struct cryptocop_prio_job **pj, struct cryptocop_operation *operation); + +static int cryptocop_job_queue_init(void); +static void cryptocop_job_queue_close(void); + +static int create_md5_pad(int alloc_flag, unsigned long long hashed_length, char **pad, size_t *pad_length); + +static int create_sha1_pad(int alloc_flag, unsigned long long hashed_length, char **pad, size_t *pad_length); + +static int transform_ok(struct cryptocop_transform_init *tinit); + +static struct cryptocop_session *get_session(cryptocop_session_id sid); + +static struct cryptocop_transform_ctx *get_transform_ctx(struct cryptocop_session *sess, cryptocop_tfrm_id tid); + +static void delete_internal_operation(struct cryptocop_int_operation *iop); + +static void get_aes_decrypt_key(unsigned char *dec_key, const unsigned char *key, unsigned int keylength); + +static int init_stream_coprocessor(void); + +static void __exit exit_stream_coprocessor(void); + +/*#define LDEBUG*/ +#ifdef LDEBUG +#define DEBUG(s) s +#define DEBUG_API(s) s +static void print_cryptocop_operation(struct cryptocop_operation *cop); +static void print_dma_descriptors(struct cryptocop_int_operation *iop); +static void print_strcop_crypto_op(struct strcop_crypto_op *cop); +static void print_lock_status(void); +static void print_user_dma_lists(struct cryptocop_dma_list_operation *dma_op); +#define assert(s) do{if (!(s)) panic(#s);} while(0); +#else +#define DEBUG(s) +#define DEBUG_API(s) +#define assert(s) +#endif + + +/* Transform constants. */ +#define DES_BLOCK_LENGTH (8) +#define AES_BLOCK_LENGTH (16) +#define MD5_BLOCK_LENGTH (64) +#define SHA1_BLOCK_LENGTH (64) +#define CSUM_BLOCK_LENGTH (2) +#define MD5_STATE_LENGTH (16) +#define SHA1_STATE_LENGTH (20) + +/* The device number. */ +#define CRYPTOCOP_MAJOR (254) +#define CRYPTOCOP_MINOR (0) + + + +struct file_operations cryptocop_fops = { + owner: THIS_MODULE, + open: cryptocop_open, + release: cryptocop_release, + ioctl: cryptocop_ioctl +}; + + +static void free_cdesc(struct cryptocop_dma_desc *cdesc) +{ + DEBUG(printk("free_cdesc: cdesc 0x%p, from_pool=%d\n", cdesc, cdesc->from_pool)); + if (cdesc->free_buf) kfree(cdesc->free_buf); + + if (cdesc->from_pool) { + unsigned long int flags; + spin_lock_irqsave(&descr_pool_lock, flags); + cdesc->next = descr_pool_free_list; + descr_pool_free_list = cdesc; + ++descr_pool_no_free; + spin_unlock_irqrestore(&descr_pool_lock, flags); + } else { + kfree(cdesc); + } +} + + +static struct cryptocop_dma_desc *alloc_cdesc(int alloc_flag) +{ + int use_pool = (alloc_flag & GFP_ATOMIC) ? 1 : 0; + struct cryptocop_dma_desc *cdesc; + + if (use_pool) { + unsigned long int flags; + spin_lock_irqsave(&descr_pool_lock, flags); + if (!descr_pool_free_list) { + spin_unlock_irqrestore(&descr_pool_lock, flags); + DEBUG_API(printk("alloc_cdesc: pool is empty\n")); + return NULL; + } + cdesc = descr_pool_free_list; + descr_pool_free_list = descr_pool_free_list->next; + --descr_pool_no_free; + spin_unlock_irqrestore(&descr_pool_lock, flags); + cdesc->from_pool = 1; + } else { + cdesc = kmalloc(sizeof(struct cryptocop_dma_desc), alloc_flag); + if (!cdesc) { + DEBUG_API(printk("alloc_cdesc: kmalloc\n")); + return NULL; + } + cdesc->from_pool = 0; + } + cdesc->dma_descr = (dma_descr_data*)(((unsigned long int)cdesc + offsetof(struct cryptocop_dma_desc, dma_descr_buf) + DESCR_ALLOC_PAD) & ~0x0000001F); + + cdesc->next = NULL; + + cdesc->free_buf = NULL; + cdesc->dma_descr->out_eop = 0; + cdesc->dma_descr->in_eop = 0; + cdesc->dma_descr->intr = 0; + cdesc->dma_descr->eol = 0; + cdesc->dma_descr->wait = 0; + cdesc->dma_descr->buf = NULL; + cdesc->dma_descr->after = NULL; + + DEBUG_API(printk("alloc_cdesc: return 0x%p, cdesc->dma_descr=0x%p, from_pool=%d\n", cdesc, cdesc->dma_descr, cdesc->from_pool)); + return cdesc; +} + + +static void setup_descr_chain(struct cryptocop_dma_desc *cd) +{ + DEBUG(printk("setup_descr_chain: entering\n")); + while (cd) { + if (cd->next) { + cd->dma_descr->next = (dma_descr_data*)virt_to_phys(cd->next->dma_descr); + } else { + cd->dma_descr->next = NULL; + } + cd = cd->next; + } + DEBUG(printk("setup_descr_chain: exit\n")); +} + + +/* Create a pad descriptor for the transform. + * Return -1 for error, 0 if pad created. */ +static int create_pad_descriptor(struct cryptocop_tfrm_ctx *tc, struct cryptocop_dma_desc **pad_desc, int alloc_flag) +{ + struct cryptocop_dma_desc *cdesc = NULL; + int error = 0; + struct strcop_meta_out mo = { + .ciphsel = src_none, + .hashsel = src_none, + .csumsel = src_none + }; + char *pad; + size_t plen; + + DEBUG(printk("create_pad_descriptor: start.\n")); + /* Setup pad descriptor. */ + + DEBUG(printk("create_pad_descriptor: setting up padding.\n")); + cdesc = alloc_cdesc(alloc_flag); + if (!cdesc){ + DEBUG_API(printk("create_pad_descriptor: alloc pad desc\n")); + goto error_cleanup; + } + switch (tc->unit_no) { + case src_md5: + error = create_md5_pad(alloc_flag, tc->consumed, &pad, &plen); + if (error){ + DEBUG_API(printk("create_pad_descriptor: create_md5_pad_failed\n")); + goto error_cleanup; + } + cdesc->free_buf = pad; + mo.hashsel = src_dma; + mo.hashconf = tc->hash_conf; + mo.hashmode = tc->hash_mode; + break; + case src_sha1: + error = create_sha1_pad(alloc_flag, tc->consumed, &pad, &plen); + if (error){ + DEBUG_API(printk("create_pad_descriptor: create_sha1_pad_failed\n")); + goto error_cleanup; + } + cdesc->free_buf = pad; + mo.hashsel = src_dma; + mo.hashconf = tc->hash_conf; + mo.hashmode = tc->hash_mode; + break; + case src_csum: + if (tc->consumed % tc->blocklength){ + pad = (char*)csum_zero_pad; + plen = 1; + } else { + pad = (char*)cdesc; /* Use any pointer. */ + plen = 0; + } + mo.csumsel = src_dma; + break; + } + cdesc->dma_descr->wait = 1; + cdesc->dma_descr->out_eop = 1; /* Since this is a pad output is pushed. EOP is ok here since the padded unit is the only one active. */ + cdesc->dma_descr->buf = (char*)virt_to_phys((char*)pad); + cdesc->dma_descr->after = cdesc->dma_descr->buf + plen; + + cdesc->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_out, mo); + *pad_desc = cdesc; + + return 0; + + error_cleanup: + if (cdesc) free_cdesc(cdesc); + return -1; +} + + +static int setup_key_dl_desc(struct cryptocop_tfrm_ctx *tc, struct cryptocop_dma_desc **kd, int alloc_flag) +{ + struct cryptocop_dma_desc *key_desc = alloc_cdesc(alloc_flag); + struct strcop_meta_out mo = {0}; + + DEBUG(printk("setup_key_dl_desc\n")); + + if (!key_desc) { + DEBUG_API(printk("setup_key_dl_desc: failed descriptor allocation.\n")); + return -ENOMEM; + } + + /* Download key. */ + if ((tc->tctx->init.alg == cryptocop_alg_aes) && (tc->tcfg->flags & CRYPTOCOP_DECRYPT)) { + /* Precook the AES decrypt key. */ + if (!tc->tctx->dec_key_set){ + get_aes_decrypt_key(tc->tctx->dec_key, tc->tctx->init.key, tc->tctx->init.keylen); + tc->tctx->dec_key_set = 1; + } + key_desc->dma_descr->buf = (char*)virt_to_phys(tc->tctx->dec_key); + key_desc->dma_descr->after = key_desc->dma_descr->buf + tc->tctx->init.keylen/8; + } else { + key_desc->dma_descr->buf = (char*)virt_to_phys(tc->tctx->init.key); + key_desc->dma_descr->after = key_desc->dma_descr->buf + tc->tctx->init.keylen/8; + } + /* Setup metadata. */ + mo.dlkey = 1; + switch (tc->tctx->init.keylen) { + case 64: + mo.decrypt = 0; + mo.hashmode = 0; + break; + case 128: + mo.decrypt = 0; + mo.hashmode = 1; + break; + case 192: + mo.decrypt = 1; + mo.hashmode = 0; + break; + case 256: + mo.decrypt = 1; + mo.hashmode = 1; + break; + default: + break; + } + mo.ciphsel = mo.hashsel = mo.csumsel = src_none; + key_desc->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_out, mo); + + key_desc->dma_descr->out_eop = 1; + key_desc->dma_descr->wait = 1; + key_desc->dma_descr->intr = 0; + + *kd = key_desc; + return 0; +} + +static int setup_cipher_iv_desc(struct cryptocop_tfrm_ctx *tc, struct cryptocop_dma_desc **id, int alloc_flag) +{ + struct cryptocop_dma_desc *iv_desc = alloc_cdesc(alloc_flag); + struct strcop_meta_out mo = {0}; + + DEBUG(printk("setup_cipher_iv_desc\n")); + + if (!iv_desc) { + DEBUG_API(printk("setup_cipher_iv_desc: failed CBC IV descriptor allocation.\n")); + return -ENOMEM; + } + /* Download IV. */ + iv_desc->dma_descr->buf = (char*)virt_to_phys(tc->tcfg->iv); + iv_desc->dma_descr->after = iv_desc->dma_descr->buf + tc->blocklength; + + /* Setup metadata. */ + mo.hashsel = mo.csumsel = src_none; + mo.ciphsel = src_dma; + mo.ciphconf = tc->ciph_conf; + mo.cbcmode = tc->cbcmode; + + iv_desc->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_out, mo); + + iv_desc->dma_descr->out_eop = 0; + iv_desc->dma_descr->wait = 1; + iv_desc->dma_descr->intr = 0; + + *id = iv_desc; + return 0; +} + +/* Map the ouput length of the transform to operation output starting on the inject index. */ +static int create_input_descriptors(struct cryptocop_operation *operation, struct cryptocop_tfrm_ctx *tc, struct cryptocop_dma_desc **id, int alloc_flag) +{ + int err = 0; + struct cryptocop_dma_desc head = {0}; + struct cryptocop_dma_desc *outdesc = &head; + size_t iov_offset = 0; + size_t out_ix = 0; + int outiov_ix = 0; + struct strcop_meta_in mi = {0}; + + size_t out_length = tc->produced; + int rem_length; + int dlength; + + assert(out_length != 0); + if (((tc->produced + tc->tcfg->inject_ix) > operation->tfrm_op.outlen) || (tc->produced && (operation->tfrm_op.outlen == 0))) { + DEBUG_API(printk("create_input_descriptors: operation outdata too small\n")); + return -EINVAL; + } + /* Traverse the out iovec until the result inject index is reached. */ + while ((outiov_ix < operation->tfrm_op.outcount) && ((out_ix + operation->tfrm_op.outdata[outiov_ix].iov_len) <= tc->tcfg->inject_ix)){ + out_ix += operation->tfrm_op.outdata[outiov_ix].iov_len; + outiov_ix++; + } + if (outiov_ix >= operation->tfrm_op.outcount){ + DEBUG_API(printk("create_input_descriptors: operation outdata too small\n")); + return -EINVAL; + } + iov_offset = tc->tcfg->inject_ix - out_ix; + mi.dmasel = tc->unit_no; + + /* Setup the output descriptors. */ + while ((out_length > 0) && (outiov_ix < operation->tfrm_op.outcount)) { + outdesc->next = alloc_cdesc(alloc_flag); + if (!outdesc->next) { + DEBUG_API(printk("create_input_descriptors: alloc_cdesc\n")); + err = -ENOMEM; + goto error_cleanup; + } + outdesc = outdesc->next; + rem_length = operation->tfrm_op.outdata[outiov_ix].iov_len - iov_offset; + dlength = (out_length < rem_length) ? out_length : rem_length; + + DEBUG(printk("create_input_descriptors:\n" + "outiov_ix=%d, rem_length=%d, dlength=%d\n" + "iov_offset=%d, outdata[outiov_ix].iov_len=%d\n" + "outcount=%d, outiov_ix=%d\n", + outiov_ix, rem_length, dlength, iov_offset, operation->tfrm_op.outdata[outiov_ix].iov_len, operation->tfrm_op.outcount, outiov_ix)); + + outdesc->dma_descr->buf = (char*)virt_to_phys(operation->tfrm_op.outdata[outiov_ix].iov_base + iov_offset); + outdesc->dma_descr->after = outdesc->dma_descr->buf + dlength; + outdesc->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_in, mi); + + out_length -= dlength; + iov_offset += dlength; + if (iov_offset >= operation->tfrm_op.outdata[outiov_ix].iov_len) { + iov_offset = 0; + ++outiov_ix; + } + } + if (out_length > 0){ + DEBUG_API(printk("create_input_descriptors: not enough room for output, %d remained\n", out_length)); + err = -EINVAL; + goto error_cleanup; + } + /* Set sync in last descriptor. */ + mi.sync = 1; + outdesc->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_in, mi); + + *id = head.next; + return 0; + + error_cleanup: + while (head.next) { + outdesc = head.next->next; + free_cdesc(head.next); + head.next = outdesc; + } + return err; +} + + +static int create_output_descriptors(struct cryptocop_operation *operation, int *iniov_ix, int *iniov_offset, size_t desc_len, struct cryptocop_dma_desc **current_out_cdesc, struct strcop_meta_out *meta_out, int alloc_flag) +{ + while (desc_len != 0) { + struct cryptocop_dma_desc *cdesc; + int rem_length = operation->tfrm_op.indata[*iniov_ix].iov_len - *iniov_offset; + int dlength = (desc_len < rem_length) ? desc_len : rem_length; + + cdesc = alloc_cdesc(alloc_flag); + if (!cdesc) { + DEBUG_API(printk("create_output_descriptors: alloc_cdesc\n")); + return -ENOMEM; + } + (*current_out_cdesc)->next = cdesc; + (*current_out_cdesc) = cdesc; + + cdesc->free_buf = NULL; + + cdesc->dma_descr->buf = (char*)virt_to_phys(operation->tfrm_op.indata[*iniov_ix].iov_base + *iniov_offset); + cdesc->dma_descr->after = cdesc->dma_descr->buf + dlength; + + desc_len -= dlength; + *iniov_offset += dlength; + assert(desc_len >= 0); + if (*iniov_offset >= operation->tfrm_op.indata[*iniov_ix].iov_len) { + *iniov_offset = 0; + ++(*iniov_ix); + if (*iniov_ix > operation->tfrm_op.incount) { + DEBUG_API(printk("create_output_descriptors: not enough indata in operation.")); + return -EINVAL; + } + } + cdesc->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_out, (*meta_out)); + } /* while (desc_len != 0) */ + /* Last DMA descriptor gets a 'wait' bit to signal expected change in metadata. */ + (*current_out_cdesc)->dma_descr->wait = 1; /* This will set extraneous WAIT in some situations, e.g. when padding hashes and checksums. */ + + return 0; +} + + +static int append_input_descriptors(struct cryptocop_operation *operation, struct cryptocop_dma_desc **current_in_cdesc, struct cryptocop_dma_desc **current_out_cdesc, struct cryptocop_tfrm_ctx *tc, int alloc_flag) +{ + DEBUG(printk("append_input_descriptors, tc=0x%p, unit_no=%d\n", tc, tc->unit_no)); + if (tc->tcfg) { + int failed = 0; + struct cryptocop_dma_desc *idescs = NULL; + DEBUG(printk("append_input_descriptors: pushing output, consumed %d produced %d bytes.\n", tc->consumed, tc->produced)); + if (tc->pad_descs) { + DEBUG(printk("append_input_descriptors: append pad descriptors to DMA out list.\n")); + while (tc->pad_descs) { + DEBUG(printk("append descriptor 0x%p\n", tc->pad_descs)); + (*current_out_cdesc)->next = tc->pad_descs; + tc->pad_descs = tc->pad_descs->next; + (*current_out_cdesc) = (*current_out_cdesc)->next; + } + } + + /* Setup and append output descriptors to DMA in list. */ + if (tc->unit_no == src_dma){ + /* mem2mem. Setup DMA in descriptors to discard all input prior to the requested mem2mem data. */ + struct strcop_meta_in mi = {.sync = 0, .dmasel = src_dma}; + unsigned int start_ix = tc->start_ix; + while (start_ix){ + unsigned int desclen = start_ix < MEM2MEM_DISCARD_BUF_LENGTH ? start_ix : MEM2MEM_DISCARD_BUF_LENGTH; + (*current_in_cdesc)->next = alloc_cdesc(alloc_flag); + if (!(*current_in_cdesc)->next){ + DEBUG_API(printk("append_input_descriptors: alloc_cdesc mem2mem discard failed\n")); + return -ENOMEM; + } + (*current_in_cdesc) = (*current_in_cdesc)->next; + (*current_in_cdesc)->dma_descr->buf = (char*)virt_to_phys(mem2mem_discard_buf); + (*current_in_cdesc)->dma_descr->after = (*current_in_cdesc)->dma_descr->buf + desclen; + (*current_in_cdesc)->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_in, mi); + start_ix -= desclen; + } + mi.sync = 1; + (*current_in_cdesc)->dma_descr->md = REG_TYPE_CONV(unsigned short int, struct strcop_meta_in, mi); + } + + failed = create_input_descriptors(operation, tc, &idescs, alloc_flag); + if (failed){ + DEBUG_API(printk("append_input_descriptors: output descriptor setup failed\n")); + return failed; + } + DEBUG(printk("append_input_descriptors: append output descriptors to DMA in list.\n")); + while (idescs) { + DEBUG(printk("append descriptor 0x%p\n", idescs)); + (*current_in_cdesc)->next = idescs; + idescs = idescs->next; + (*current_in_cdesc) = (*current_in_cdesc)->next; + } + } + return 0; +} + + + +static int cryptocop_setup_dma_list(struct cryptocop_operation *operation, struct cryptocop_int_operation **int_op, int alloc_flag) +{ + struct cryptocop_session *sess; + struct cryptocop_transform_ctx *tctx; + + struct cryptocop_tfrm_ctx digest_ctx = { + .previous_src = src_none, + .current_src = src_none, + .start_ix = 0, + .requires_padding = 1, + .strict_block_length = 0, + .hash_conf = 0, + .hash_mode = 0, + .ciph_conf = 0, + .cbcmode = 0, + .decrypt = 0, + .consumed = 0, + .produced = 0, + .pad_descs = NULL, + .active = 0, + .done = 0, + .prev_src = NULL, + .curr_src = NULL, + .tcfg = NULL}; + struct cryptocop_tfrm_ctx cipher_ctx = { + .previous_src = src_none, + .current_src = src_none, + .start_ix = 0, + .requires_padding = 0, + .strict_block_length = 1, + .hash_conf = 0, + .hash_mode = 0, + .ciph_conf = 0, + .cbcmode = 0, + .decrypt = 0, + .consumed = 0, + .produced = 0, + .pad_descs = NULL, + .active = 0, + .done = 0, + .prev_src = NULL, + .curr_src = NULL, + .tcfg = NULL}; + struct cryptocop_tfrm_ctx csum_ctx = { + .previous_src = src_none, + .current_src = src_none, + .start_ix = 0, + .blocklength = 2, + .requires_padding = 1, + .strict_block_length = 0, + .hash_conf = 0, + .hash_mode = 0, + .ciph_conf = 0, + .cbcmode = 0, + .decrypt = 0, + .consumed = 0, + .produced = 0, + .pad_descs = NULL, + .active = 0, + .done = 0, + .tcfg = NULL, + .prev_src = NULL, + .curr_src = NULL, + .unit_no = src_csum}; + struct cryptocop_tfrm_cfg *tcfg = operation->tfrm_op.tfrm_cfg; + + unsigned int indata_ix = 0; + + /* iovec accounting. */ + int iniov_ix = 0; + int iniov_offset = 0; + + /* Operation descriptor cfg traversal pointer. */ + struct cryptocop_desc *odsc; + + int failed = 0; + /* List heads for allocated descriptors. */ + struct cryptocop_dma_desc out_cdesc_head = {0}; + struct cryptocop_dma_desc in_cdesc_head = {0}; + + struct cryptocop_dma_desc *current_out_cdesc = &out_cdesc_head; + struct cryptocop_dma_desc *current_in_cdesc = &in_cdesc_head; + + struct cryptocop_tfrm_ctx *output_tc = NULL; + void *iop_alloc_ptr; + + assert(operation != NULL); + assert(int_op != NULL); + + DEBUG(printk("cryptocop_setup_dma_list: start\n")); + DEBUG(print_cryptocop_operation(operation)); + + sess = get_session(operation->sid); + if (!sess) { + DEBUG_API(printk("cryptocop_setup_dma_list: no session found for operation.\n")); + failed = -EINVAL; + goto error_cleanup; + } + iop_alloc_ptr = kmalloc(DESCR_ALLOC_PAD + sizeof(struct cryptocop_int_operation), alloc_flag); + if (!iop_alloc_ptr) { + DEBUG_API(printk("cryptocop_setup_dma_list: kmalloc cryptocop_int_operation\n")); + failed = -ENOMEM; + goto error_cleanup; + } + (*int_op) = (struct cryptocop_int_operation*)(((unsigned long int)(iop_alloc_ptr + DESCR_ALLOC_PAD + offsetof(struct cryptocop_int_operation, ctx_out)) & ~0x0000001F) - offsetof(struct cryptocop_int_operation, ctx_out)); + DEBUG(memset((*int_op), 0xff, sizeof(struct cryptocop_int_operation))); + (*int_op)->alloc_ptr = iop_alloc_ptr; + DEBUG(printk("cryptocop_setup_dma_list: *int_op=0x%p, alloc_ptr=0x%p\n", *int_op, (*int_op)->alloc_ptr)); + + (*int_op)->sid = operation->sid; + (*int_op)->cdesc_out = NULL; + (*int_op)->cdesc_in = NULL; + (*int_op)->tdes_mode = cryptocop_3des_ede; + (*int_op)->csum_mode = cryptocop_csum_le; + (*int_op)->ddesc_out = NULL; + (*int_op)->ddesc_in = NULL; + + /* Scan operation->tfrm_op.tfrm_cfg for bad configuration and set up the local contexts. */ + if (!tcfg) { + DEBUG_API(printk("cryptocop_setup_dma_list: no configured transforms in operation.\n")); + failed = -EINVAL; + goto error_cleanup; + } + while (tcfg) { + tctx = get_transform_ctx(sess, tcfg->tid); + if (!tctx) { + DEBUG_API(printk("cryptocop_setup_dma_list: no transform id %d in session.\n", tcfg->tid)); + failed = -EINVAL; + goto error_cleanup; + } + if (tcfg->inject_ix > operation->tfrm_op.outlen){ + DEBUG_API(printk("cryptocop_setup_dma_list: transform id %d inject_ix (%d) > operation->tfrm_op.outlen(%d)", tcfg->tid, tcfg->inject_ix, operation->tfrm_op.outlen)); + failed = -EINVAL; + goto error_cleanup; + } + switch (tctx->init.alg){ + case cryptocop_alg_mem2mem: + if (cipher_ctx.tcfg != NULL){ + DEBUG_API(printk("cryptocop_setup_dma_list: multiple ciphers in operation.\n")); + failed = -EINVAL; + goto error_cleanup; + } + /* mem2mem is handled as a NULL cipher. */ + cipher_ctx.cbcmode = 0; + cipher_ctx.decrypt = 0; + cipher_ctx.blocklength = 1; + cipher_ctx.ciph_conf = 0; + cipher_ctx.unit_no = src_dma; + cipher_ctx.tcfg = tcfg; + cipher_ctx.tctx = tctx; + break; + case cryptocop_alg_des: + case cryptocop_alg_3des: + case cryptocop_alg_aes: + /* cipher */ + if (cipher_ctx.tcfg != NULL){ + DEBUG_API(printk("cryptocop_setup_dma_list: multiple ciphers in operation.\n")); + failed = -EINVAL; + goto error_cleanup; + } + cipher_ctx.tcfg = tcfg; + cipher_ctx.tctx = tctx; + if (cipher_ctx.tcfg->flags & CRYPTOCOP_DECRYPT){ + cipher_ctx.decrypt = 1; + } + switch (tctx->init.cipher_mode) { + case cryptocop_cipher_mode_ecb: + cipher_ctx.cbcmode = 0; + break; + case cryptocop_cipher_mode_cbc: + cipher_ctx.cbcmode = 1; + break; + default: + DEBUG_API(printk("cryptocop_setup_dma_list: cipher_ctx, bad cipher mode==%d\n", tctx->init.cipher_mode)); + failed = -EINVAL; + goto error_cleanup; + } + DEBUG(printk("cryptocop_setup_dma_list: cipher_ctx, set CBC mode==%d\n", cipher_ctx.cbcmode)); + switch (tctx->init.alg){ + case cryptocop_alg_des: + cipher_ctx.ciph_conf = 0; + cipher_ctx.unit_no = src_des; + cipher_ctx.blocklength = DES_BLOCK_LENGTH; + break; + case cryptocop_alg_3des: + cipher_ctx.ciph_conf = 1; + cipher_ctx.unit_no = src_des; + cipher_ctx.blocklength = DES_BLOCK_LENGTH; + break; + case cryptocop_alg_aes: + cipher_ctx.ciph_conf = 2; + cipher_ctx.unit_no = src_aes; + cipher_ctx.blocklength = AES_BLOCK_LENGTH; + break; + default: + panic("cryptocop_setup_dma_list: impossible algorithm %d\n", tctx->init.alg); + } + (*int_op)->tdes_mode = tctx->init.tdes_mode; + break; + case cryptocop_alg_md5: + case cryptocop_alg_sha1: + /* digest */ + if (digest_ctx.tcfg != NULL){ + DEBUG_API(printk("cryptocop_setup_dma_list: multiple digests in operation.\n")); + failed = -EINVAL; + goto error_cleanup; + } + digest_ctx.tcfg = tcfg; + digest_ctx.tctx = tctx; + digest_ctx.hash_mode = 0; /* Don't use explicit IV in this API. */ + switch (tctx->init.alg){ + case cryptocop_alg_md5: + digest_ctx.blocklength = MD5_BLOCK_LENGTH; + digest_ctx.unit_no = src_md5; + digest_ctx.hash_conf = 1; /* 1 => MD-5 */ + break; + case cryptocop_alg_sha1: + digest_ctx.blocklength = SHA1_BLOCK_LENGTH; + digest_ctx.unit_no = src_sha1; + digest_ctx.hash_conf = 0; /* 0 => SHA-1 */ + break; + default: + panic("cryptocop_setup_dma_list: impossible digest algorithm\n"); + } + break; + case cryptocop_alg_csum: + /* digest */ + if (csum_ctx.tcfg != NULL){ + DEBUG_API(printk("cryptocop_setup_dma_list: multiple checksums in operation.\n")); + failed = -EINVAL; + goto error_cleanup; + } + (*int_op)->csum_mode = tctx->init.csum_mode; + csum_ctx.tcfg = tcfg; + csum_ctx.tctx = tctx; + break; + default: + /* no algorithm. */ + DEBUG_API(printk("cryptocop_setup_dma_list: invalid algorithm %d specified in tfrm %d.\n", tctx->init.alg, tcfg->tid)); + failed = -EINVAL; + goto error_cleanup; + } + tcfg = tcfg->next; + } + /* Download key if a cipher is used. */ + if (cipher_ctx.tcfg && (cipher_ctx.tctx->init.alg != cryptocop_alg_mem2mem)){ + struct cryptocop_dma_desc *key_desc = NULL; + + failed = setup_key_dl_desc(&cipher_ctx, &key_desc, alloc_flag); + if (failed) { + DEBUG_API(printk("cryptocop_setup_dma_list: setup key dl\n")); + goto error_cleanup; + } + current_out_cdesc->next = key_desc; + current_out_cdesc = key_desc; + indata_ix += (unsigned int)(key_desc->dma_descr->after - key_desc->dma_descr->buf); + + /* Download explicit IV if a cipher is used and CBC mode and explicit IV selected. */ + if ((cipher_ctx.tctx->init.cipher_mode == cryptocop_cipher_mode_cbc) && (cipher_ctx.tcfg->flags & CRYPTOCOP_EXPLICIT_IV)) { + struct cryptocop_dma_desc *iv_desc = NULL; + + DEBUG(printk("cryptocop_setup_dma_list: setup cipher CBC IV descriptor.\n")); + + failed = setup_cipher_iv_desc(&cipher_ctx, &iv_desc, alloc_flag); + if (failed) { + DEBUG_API(printk("cryptocop_setup_dma_list: CBC IV descriptor.\n")); + goto error_cleanup; + } + current_out_cdesc->next = iv_desc; + current_out_cdesc = iv_desc; + indata_ix += (unsigned int)(iv_desc->dma_descr->after - iv_desc->dma_descr->buf); + } + } + + /* Process descriptors. */ + odsc = operation->tfrm_op.desc; + while (odsc) { + struct cryptocop_desc_cfg *dcfg = odsc->cfg; + struct strcop_meta_out meta_out = {0}; + size_t desc_len = odsc->length; + int active_count, eop_needed_count; + + output_tc = NULL; + + DEBUG(printk("cryptocop_setup_dma_list: parsing an operation descriptor\n")); + + while (dcfg) { + struct cryptocop_tfrm_ctx *tc = NULL; + + DEBUG(printk("cryptocop_setup_dma_list: parsing an operation descriptor configuration.\n")); + /* Get the local context for the transform and mark it as the output unit if it produces output. */ + if (digest_ctx.tcfg && (digest_ctx.tcfg->tid == dcfg->tid)){ + tc = &digest_ctx; + } else if (cipher_ctx.tcfg && (cipher_ctx.tcfg->tid == dcfg->tid)){ + tc = &cipher_ctx; + } else if (csum_ctx.tcfg && (csum_ctx.tcfg->tid == dcfg->tid)){ + tc = &csum_ctx; + } + if (!tc) { + DEBUG_API(printk("cryptocop_setup_dma_list: invalid transform %d specified in descriptor.\n", dcfg->tid)); + failed = -EINVAL; + goto error_cleanup; + } + if (tc->done) { + DEBUG_API(printk("cryptocop_setup_dma_list: completed transform %d reused.\n", dcfg->tid)); + failed = -EINVAL; + goto error_cleanup; + } + if (!tc->active) { + tc->start_ix = indata_ix; + tc->active = 1; + } + + tc->previous_src = tc->current_src; + tc->prev_src = tc->curr_src; + /* Map source unit id to DMA source config. */ + switch (dcfg->src){ + case cryptocop_source_dma: + tc->current_src = src_dma; + break; + case cryptocop_source_des: + tc->current_src = src_des; + break; + case cryptocop_source_3des: + tc->current_src = src_des; + break; + case cryptocop_source_aes: + tc->current_src = src_aes; + break; + case cryptocop_source_md5: + case cryptocop_source_sha1: + case cryptocop_source_csum: + case cryptocop_source_none: + default: + /* We do not allow using accumulating style units (SHA-1, MD5, checksum) as sources to other units. + */ + DEBUG_API(printk("cryptocop_setup_dma_list: bad unit source configured %d.\n", dcfg->src)); + failed = -EINVAL; + goto error_cleanup; + } + if (tc->current_src != src_dma) { + /* Find the unit we are sourcing from. */ + if (digest_ctx.unit_no == tc->current_src){ + tc->curr_src = &digest_ctx; + } else if (cipher_ctx.unit_no == tc->current_src){ + tc->curr_src = &cipher_ctx; + } else if (csum_ctx.unit_no == tc->current_src){ + tc->curr_src = &csum_ctx; + } + if ((tc->curr_src == tc) && (tc->unit_no != src_dma)){ + DEBUG_API(printk("cryptocop_setup_dma_list: unit %d configured to source from itself.\n", tc->unit_no)); + failed = -EINVAL; + goto error_cleanup; + } + } else { + tc->curr_src = NULL; + } + + /* Detect source switch. */ + DEBUG(printk("cryptocop_setup_dma_list: tc->active=%d tc->unit_no=%d tc->current_src=%d tc->previous_src=%d, tc->curr_src=0x%p, tc->prev_srv=0x%p\n", tc->active, tc->unit_no, tc->current_src, tc->previous_src, tc->curr_src, tc->prev_src)); + if (tc->active && (tc->current_src != tc->previous_src)) { + /* Only allow source switch when both the old source unit and the new one have + * no pending data to process (i.e. the consumed length must be a multiple of the + * transform blocklength). */ + /* Note: if the src == NULL we are actually sourcing from DMA out. */ + if (((tc->prev_src != NULL) && (tc->prev_src->consumed % tc->prev_src->blocklength)) || + ((tc->curr_src != NULL) && (tc->curr_src->consumed % tc->curr_src->blocklength))) + { + DEBUG_API(printk("cryptocop_setup_dma_list: can only disconnect from or connect to a unit on a multiple of the blocklength, old: cons=%d, prod=%d, block=%d, new: cons=%d prod=%d, block=%d.\n", tc->prev_src ? tc->prev_src |