blob: b9262a78dd6edd2e24f6894b5909030262741543 [file] [log] [blame]
/*
*
* sep_crypto.c - Crypto interface structures
*
* Copyright(c) 2009-2011 Intel Corporation. All rights reserved.
* Contributions(c) 2009-2010 Discretix. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59
* Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* CONTACTS:
*
* Mark Allyn mark.a.allyn@intel.com
* Jayant Mangalampalli jayant.mangalampalli@intel.com
*
* CHANGES:
*
* 2009.06.26 Initial publish
* 2010.09.14 Upgrade to Medfield
* 2011.02.22 Enable Kernel Crypto
*
*/
/* #define DEBUG */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/list.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/workqueue.h>
#include <linux/crypto.h>
#include <crypto/internal/hash.h>
#include <crypto/scatterwalk.h>
#include <crypto/sha.h>
#include <crypto/md5.h>
#include <crypto/aes.h>
#include <crypto/des.h>
#include <crypto/hash.h>
#include "sep_driver_hw_defs.h"
#include "sep_driver_config.h"
#include "sep_driver_api.h"
#include "sep_dev.h"
#include "sep_crypto.h"
#if defined(CONFIG_CRYPTO) || defined(CONFIG_CRYPTO_MODULE)
/* Globals for queuing */
static spinlock_t queue_lock;
static struct crypto_queue sep_queue;
/* Declare of dequeuer */
static void sep_dequeuer(void *data);
/* TESTING */
/**
* sep_do_callback
* @work: pointer to work_struct
* This is what is called by the queue; it is generic so that it
* can be used by any type of operation as each different callback
* function can use the data parameter in its own way
*/
static void sep_do_callback(struct work_struct *work)
{
struct sep_work_struct *sep_work = container_of(work,
struct sep_work_struct, work);
if (sep_work != NULL) {
(sep_work->callback)(sep_work->data);
kfree(sep_work);
} else {
pr_debug("sep crypto: do callback - NULL container\n");
}
}
/**
* sep_submit_work
* @work_queue: pointer to struct_workqueue
* @funct: pointer to function to execute
* @data: pointer to data; function will know
* how to use it
* This is a generic API to submit something to
* the queue. The callback function will depend
* on what operation is to be done
*/
static int sep_submit_work(struct workqueue_struct *work_queue,
void(*funct)(void *),
void *data)
{
struct sep_work_struct *sep_work;
int result;
sep_work = kmalloc(sizeof(struct sep_work_struct), GFP_ATOMIC);
if (sep_work == NULL) {
pr_debug("sep crypto: cant allocate work structure\n");
return -ENOMEM;
}
sep_work->callback = funct;
sep_work->data = data;
INIT_WORK(&sep_work->work, sep_do_callback);
result = queue_work(work_queue, &sep_work->work);
if (!result) {
pr_debug("sep_crypto: queue_work failed\n");
return -EINVAL;
}
return 0;
}
/**
* sep_alloc_sg_buf -
* @sep: pointer to struct sep_device
* @size: total size of area
* @block_size: minimum size of chunks
* each page is minimum or modulo this size
* @returns: pointer to struct scatterlist for new
* buffer
**/
static struct scatterlist *sep_alloc_sg_buf(
struct sep_device *sep,
size_t size,
size_t block_size)
{
u32 nbr_pages;
u32 ct1;
void *buf;
size_t current_size;
size_t real_page_size;
struct scatterlist *sg, *sg_temp;
if (size == 0)
return NULL;
dev_dbg(&sep->pdev->dev, "sep alloc sg buf\n");
current_size = 0;
nbr_pages = 0;
real_page_size = PAGE_SIZE - (PAGE_SIZE % block_size);
/**
* The size of each page must be modulo of the operation
* block size; increment by the modified page size until
* the total size is reached, then you have the number of
* pages
*/
while (current_size < size) {
current_size += real_page_size;
nbr_pages += 1;
}
sg = kmalloc_array(nbr_pages, sizeof(struct scatterlist), GFP_ATOMIC);
if (!sg)
return NULL;
sg_init_table(sg, nbr_pages);
current_size = 0;
sg_temp = sg;
for (ct1 = 0; ct1 < nbr_pages; ct1 += 1) {
buf = (void *)get_zeroed_page(GFP_ATOMIC);
if (!buf) {
dev_warn(&sep->pdev->dev,
"Cannot allocate page for new buffer\n");
kfree(sg);
return NULL;
}
sg_set_buf(sg_temp, buf, real_page_size);
if ((size - current_size) > real_page_size) {
sg_temp->length = real_page_size;
current_size += real_page_size;
} else {
sg_temp->length = (size - current_size);
current_size = size;
}
sg_temp = sg_next(sg);
}
return sg;
}
/**
* sep_free_sg_buf -
* @sg: pointer to struct scatterlist; points to area to free
*/
static void sep_free_sg_buf(struct scatterlist *sg)
{
struct scatterlist *sg_temp = sg;
while (sg_temp) {
free_page((unsigned long)sg_virt(sg_temp));
sg_temp = sg_next(sg_temp);
}
kfree(sg);
}
/**
* sep_copy_sg -
* @sep: pointer to struct sep_device
* @sg_src: pointer to struct scatterlist for source
* @sg_dst: pointer to struct scatterlist for destination
* @size: size (in bytes) of data to copy
*
* Copy data from one scatterlist to another; both must
* be the same size
*/
static void sep_copy_sg(
struct sep_device *sep,
struct scatterlist *sg_src,
struct scatterlist *sg_dst,
size_t size)
{
u32 seg_size;
u32 in_offset, out_offset;
u32 count = 0;
struct scatterlist *sg_src_tmp = sg_src;
struct scatterlist *sg_dst_tmp = sg_dst;
in_offset = 0;
out_offset = 0;
dev_dbg(&sep->pdev->dev, "sep copy sg\n");
if ((sg_src == NULL) || (sg_dst == NULL) || (size == 0))
return;
dev_dbg(&sep->pdev->dev, "sep copy sg not null\n");
while (count < size) {
if ((sg_src_tmp->length - in_offset) >
(sg_dst_tmp->length - out_offset))
seg_size = sg_dst_tmp->length - out_offset;
else
seg_size = sg_src_tmp->length - in_offset;
if (seg_size > (size - count))
seg_size = (size = count);
memcpy(sg_virt(sg_dst_tmp) + out_offset,
sg_virt(sg_src_tmp) + in_offset,
seg_size);
in_offset += seg_size;
out_offset += seg_size;
count += seg_size;
if (in_offset >= sg_src_tmp->length) {
sg_src_tmp = sg_next(sg_src_tmp);
in_offset = 0;
}
if (out_offset >= sg_dst_tmp->length) {
sg_dst_tmp = sg_next(sg_dst_tmp);
out_offset = 0;
}
}
}
/**
* sep_oddball_pages -
* @sep: pointer to struct sep_device
* @sg: pointer to struct scatterlist - buffer to check
* @size: total data size
* @blocksize: minimum block size; must be multiples of this size
* @to_copy: 1 means do copy, 0 means do not copy
* @new_sg: pointer to location to put pointer to new sg area
* @returns: 1 if new scatterlist is needed; 0 if not needed;
* error value if operation failed
*
* The SEP device requires all pages to be multiples of the
* minimum block size appropriate for the operation
* This function check all pages; if any are oddball sizes
* (not multiple of block sizes), it creates a new scatterlist.
* If the to_copy parameter is set to 1, then a scatter list
* copy is performed. The pointer to the new scatterlist is
* put into the address supplied by the new_sg parameter; if
* no new scatterlist is needed, then a NULL is put into
* the location at new_sg.
*
*/
static int sep_oddball_pages(
struct sep_device *sep,
struct scatterlist *sg,
size_t data_size,
u32 block_size,
struct scatterlist **new_sg,
u32 do_copy)
{
struct scatterlist *sg_temp;
u32 flag;
u32 nbr_pages, page_count;
dev_dbg(&sep->pdev->dev, "sep oddball\n");
if ((sg == NULL) || (data_size == 0) || (data_size < block_size))
return 0;
dev_dbg(&sep->pdev->dev, "sep oddball not null\n");
flag = 0;
nbr_pages = 0;
page_count = 0;
sg_temp = sg;
while (sg_temp) {
nbr_pages += 1;
sg_temp = sg_next(sg_temp);
}
sg_temp = sg;
while ((sg_temp) && (flag == 0)) {
page_count += 1;
if (sg_temp->length % block_size)
flag = 1;
else
sg_temp = sg_next(sg_temp);
}
/* Do not process if last (or only) page is oddball */
if (nbr_pages == page_count)
flag = 0;
if (flag) {
dev_dbg(&sep->pdev->dev, "sep oddball processing\n");
*new_sg = sep_alloc_sg_buf(sep, data_size, block_size);
if (*new_sg == NULL) {
dev_warn(&sep->pdev->dev, "cannot allocate new sg\n");
return -ENOMEM;
}
if (do_copy)
sep_copy_sg(sep, sg, *new_sg, data_size);
return 1;
} else {
return 0;
}
}
/**
* sep_copy_offset_sg -
* @sep: pointer to struct sep_device;
* @sg: pointer to struct scatterlist
* @offset: offset into scatterlist memory
* @dst: place to put data
* @len: length of data
* @returns: number of bytes copies
*
* This copies data from scatterlist buffer
* offset from beginning - it is needed for
* handling tail data in hash
*/
static size_t sep_copy_offset_sg(
struct sep_device *sep,
struct scatterlist *sg,
u32 offset,
void *dst,
u32 len)
{
size_t page_start;
size_t page_end;
size_t offset_within_page;
size_t length_within_page;
size_t length_remaining;
size_t current_offset;
/* Find which page is beginning of segment */
page_start = 0;
page_end = sg->length;
while ((sg) && (offset > page_end)) {
page_start += sg->length;
sg = sg_next(sg);
if (sg)
page_end += sg->length;
}
if (sg == NULL)
return -ENOMEM;
offset_within_page = offset - page_start;
if ((sg->length - offset_within_page) >= len) {
/* All within this page */
memcpy(dst, sg_virt(sg) + offset_within_page, len);
return len;
} else {
/* Scattered multiple pages */
current_offset = 0;
length_remaining = len;
while ((sg) && (current_offset < len)) {
length_within_page = sg->length - offset_within_page;
if (length_within_page >= length_remaining) {
memcpy(dst+current_offset,
sg_virt(sg) + offset_within_page,
length_remaining);
length_remaining = 0;
current_offset = len;
} else {
memcpy(dst+current_offset,
sg_virt(sg) + offset_within_page,
length_within_page);
length_remaining -= length_within_page;
current_offset += length_within_page;
offset_within_page = 0;
sg = sg_next(sg);
}
}
if (sg == NULL)
return -ENOMEM;
}
return len;
}
/**
* partial_overlap -
* @src_ptr: source pointer
* @dst_ptr: destination pointer
* @nbytes: number of bytes
* @returns: 0 for success; -1 for failure
* We cannot have any partial overlap. Total overlap
* where src is the same as dst is okay
*/
static int partial_overlap(void *src_ptr, void *dst_ptr, u32 nbytes)
{
/* Check for partial overlap */
if (src_ptr != dst_ptr) {
if (src_ptr < dst_ptr) {
if ((src_ptr + nbytes) > dst_ptr)
return -EINVAL;
} else {
if ((dst_ptr + nbytes) > src_ptr)
return -EINVAL;
}
}
return 0;
}
/* Debug - prints only if DEBUG is defined */
static void sep_dump_ivs(struct ablkcipher_request *req, char *reason)
{
unsigned char *cptr;
struct sep_aes_internal_context *aes_internal;
struct sep_des_internal_context *des_internal;
int ct1;
struct this_task_ctx *ta_ctx;
struct crypto_ablkcipher *tfm;
struct sep_system_ctx *sctx;
ta_ctx = ablkcipher_request_ctx(req);
tfm = crypto_ablkcipher_reqtfm(req);
sctx = crypto_ablkcipher_ctx(tfm);
dev_dbg(&ta_ctx->sep_used->pdev->dev, "IV DUMP - %s\n", reason);
if ((ta_ctx->current_request == DES_CBC) &&
(ta_ctx->des_opmode == SEP_DES_CBC)) {
des_internal = (struct sep_des_internal_context *)
sctx->des_private_ctx.ctx_buf;
/* print vendor */
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"sep - vendor iv for DES\n");
cptr = (unsigned char *)des_internal->iv_context;
for (ct1 = 0; ct1 < crypto_ablkcipher_ivsize(tfm); ct1 += 1)
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"%02x\n", *(cptr + ct1));
/* print walk */
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"sep - walk from kernel crypto iv for DES\n");
cptr = (unsigned char *)ta_ctx->walk.iv;
for (ct1 = 0; ct1 < crypto_ablkcipher_ivsize(tfm); ct1 += 1)
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"%02x\n", *(cptr + ct1));
} else if ((ta_ctx->current_request == AES_CBC) &&
(ta_ctx->aes_opmode == SEP_AES_CBC)) {
aes_internal = (struct sep_aes_internal_context *)
sctx->aes_private_ctx.cbuff;
/* print vendor */
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"sep - vendor iv for AES\n");
cptr = (unsigned char *)aes_internal->aes_ctx_iv;
for (ct1 = 0; ct1 < crypto_ablkcipher_ivsize(tfm); ct1 += 1)
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"%02x\n", *(cptr + ct1));
/* print walk */
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"sep - walk from kernel crypto iv for AES\n");
cptr = (unsigned char *)ta_ctx->walk.iv;
for (ct1 = 0; ct1 < crypto_ablkcipher_ivsize(tfm); ct1 += 1)
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"%02x\n", *(cptr + ct1));
}
}
/**
* RFC2451: Weak key check
* Returns: 1 (weak), 0 (not weak)
*/
static int sep_weak_key(const u8 *key, unsigned int keylen)
{
static const u8 parity[] = {
8, 1, 0, 8, 0, 8, 8, 0, 0, 8, 8, 0, 8, 0, 2, 8,
0, 8, 8, 0, 8, 0, 0, 8, 8,
0, 0, 8, 0, 8, 8, 3,
0, 8, 8, 0, 8, 0, 0, 8, 8, 0, 0, 8, 0, 8, 8, 0,
8, 0, 0, 8, 0, 8, 8, 0, 0,
8, 8, 0, 8, 0, 0, 8,
0, 8, 8, 0, 8, 0, 0, 8, 8, 0, 0, 8, 0, 8, 8, 0,
8, 0, 0, 8, 0, 8, 8, 0, 0,
8, 8, 0, 8, 0, 0, 8,
8, 0, 0, 8, 0, 8, 8, 0, 0, 8, 8, 0, 8, 0, 0, 8,
0, 8, 8, 0, 8, 0, 0, 8, 8,
0, 0, 8, 0, 8, 8, 0,
0, 8, 8, 0, 8, 0, 0, 8, 8, 0, 0, 8, 0, 8, 8, 0,
8, 0, 0, 8, 0, 8, 8, 0, 0,
8, 8, 0, 8, 0, 0, 8,
8, 0, 0, 8, 0, 8, 8, 0, 0, 8, 8, 0, 8, 0, 0, 8,
0, 8, 8, 0, 8, 0, 0, 8, 8,
0, 0, 8, 0, 8, 8, 0,
8, 0, 0, 8, 0, 8, 8, 0, 0, 8, 8, 0, 8, 0, 0, 8,
0, 8, 8, 0, 8, 0, 0, 8, 8,
0, 0, 8, 0, 8, 8, 0,
4, 8, 8, 0, 8, 0, 0, 8, 8, 0, 0, 8, 0, 8, 8, 0,
8, 5, 0, 8, 0, 8, 8, 0, 0,
8, 8, 0, 8, 0, 6, 8,
};
u32 n, w;
n = parity[key[0]]; n <<= 4;
n |= parity[key[1]]; n <<= 4;
n |= parity[key[2]]; n <<= 4;
n |= parity[key[3]]; n <<= 4;
n |= parity[key[4]]; n <<= 4;
n |= parity[key[5]]; n <<= 4;
n |= parity[key[6]]; n <<= 4;
n |= parity[key[7]];
w = 0x88888888L;
/* 1 in 10^10 keys passes this test */
if (!((n - (w >> 3)) & w)) {
if (n < 0x41415151) {
if (n < 0x31312121) {
if (n < 0x14141515) {
/* 01 01 01 01 01 01 01 01 */
if (n == 0x11111111)
goto weak;
/* 01 1F 01 1F 01 0E 01 0E */
if (n == 0x13131212)
goto weak;
} else {
/* 01 E0 01 E0 01 F1 01 F1 */
if (n == 0x14141515)
goto weak;
/* 01 FE 01 FE 01 FE 01 FE */
if (n == 0x16161616)
goto weak;
}
} else {
if (n < 0x34342525) {
/* 1F 01 1F 01 0E 01 0E 01 */
if (n == 0x31312121)
goto weak;
/* 1F 1F 1F 1F 0E 0E 0E 0E (?) */
if (n == 0x33332222)
goto weak;
} else {
/* 1F E0 1F E0 0E F1 0E F1 */
if (n == 0x34342525)
goto weak;
/* 1F FE 1F FE 0E FE 0E FE */
if (n == 0x36362626)
goto weak;
}
}
} else {
if (n < 0x61616161) {
if (n < 0x44445555) {
/* E0 01 E0 01 F1 01 F1 01 */
if (n == 0x41415151)
goto weak;
/* E0 1F E0 1F F1 0E F1 0E */
if (n == 0x43435252)
goto weak;
} else {
/* E0 E0 E0 E0 F1 F1 F1 F1 (?) */
if (n == 0x44445555)
goto weak;
/* E0 FE E0 FE F1 FE F1 FE */
if (n == 0x46465656)
goto weak;
}
} else {
if (n < 0x64646565) {
/* FE 01 FE 01 FE 01 FE 01 */
if (n == 0x61616161)
goto weak;
/* FE 1F FE 1F FE 0E FE 0E */
if (n == 0x63636262)
goto weak;
} else {
/* FE E0 FE E0 FE F1 FE F1 */
if (n == 0x64646565)
goto weak;
/* FE FE FE FE FE FE FE FE */
if (n == 0x66666666)
goto weak;
}
}
}
}
return 0;
weak:
return 1;
}
/**
* sep_sg_nents
*/
static u32 sep_sg_nents(struct scatterlist *sg)
{
u32 ct1 = 0;
while (sg) {
ct1 += 1;
sg = sg_next(sg);
}
return ct1;
}
/**
* sep_start_msg -
* @ta_ctx: pointer to struct this_task_ctx
* @returns: offset to place for the next word in the message
* Set up pointer in message pool for new message
*/
static u32 sep_start_msg(struct this_task_ctx *ta_ctx)
{
u32 *word_ptr;
ta_ctx->msg_len_words = 2;
ta_ctx->msgptr = ta_ctx->msg;
memset(ta_ctx->msg, 0, SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES);
ta_ctx->msgptr += sizeof(u32) * 2;
word_ptr = (u32 *)ta_ctx->msgptr;
*word_ptr = SEP_START_MSG_TOKEN;
return sizeof(u32) * 2;
}
/**
* sep_end_msg -
* @ta_ctx: pointer to struct this_task_ctx
* @messages_offset: current message offset
* Returns: 0 for success; <0 otherwise
* End message; set length and CRC; and
* send interrupt to the SEP
*/
static void sep_end_msg(struct this_task_ctx *ta_ctx, u32 msg_offset)
{
u32 *word_ptr;
/* Msg size goes into msg after token */
ta_ctx->msg_len_words = msg_offset / sizeof(u32) + 1;
word_ptr = (u32 *)ta_ctx->msgptr;
word_ptr += 1;
*word_ptr = ta_ctx->msg_len_words;
/* CRC (currently 0) goes at end of msg */
word_ptr = (u32 *)(ta_ctx->msgptr + msg_offset);
*word_ptr = 0;
}
/**
* sep_start_inbound_msg -
* @ta_ctx: pointer to struct this_task_ctx
* @msg_offset: offset to place for the next word in the message
* @returns: 0 for success; error value for failure
* Set up pointer in message pool for inbound message
*/
static u32 sep_start_inbound_msg(struct this_task_ctx *ta_ctx, u32 *msg_offset)
{
u32 *word_ptr;
u32 token;
u32 error = SEP_OK;
*msg_offset = sizeof(u32) * 2;
word_ptr = (u32 *)ta_ctx->msgptr;
token = *word_ptr;
ta_ctx->msg_len_words = *(word_ptr + 1);
if (token != SEP_START_MSG_TOKEN) {
error = SEP_INVALID_START;
goto end_function;
}
end_function:
return error;
}
/**
* sep_write_msg -
* @ta_ctx: pointer to struct this_task_ctx
* @in_addr: pointer to start of parameter
* @size: size of parameter to copy (in bytes)
* @max_size: size to move up offset; SEP mesg is in word sizes
* @msg_offset: pointer to current offset (is updated)
* @byte_array: flag ti indicate whether endian must be changed
* Copies data into the message area from caller
*/
static void sep_write_msg(struct this_task_ctx *ta_ctx, void *in_addr,
u32 size, u32 max_size, u32 *msg_offset, u32 byte_array)
{
u32 *word_ptr;
void *void_ptr;
void_ptr = ta_ctx->msgptr + *msg_offset;
word_ptr = (u32 *)void_ptr;
memcpy(void_ptr, in_addr, size);
*msg_offset += max_size;
/* Do we need to manipulate endian? */
if (byte_array) {
u32 i;
for (i = 0; i < ((size + 3) / 4); i += 1)
*(word_ptr + i) = CHG_ENDIAN(*(word_ptr + i));
}
}
/**
* sep_make_header
* @ta_ctx: pointer to struct this_task_ctx
* @msg_offset: pointer to current offset (is updated)
* @op_code: op code to put into message
* Puts op code into message and updates offset
*/
static void sep_make_header(struct this_task_ctx *ta_ctx, u32 *msg_offset,
u32 op_code)
{
u32 *word_ptr;
*msg_offset = sep_start_msg(ta_ctx);
word_ptr = (u32 *)(ta_ctx->msgptr + *msg_offset);
*word_ptr = op_code;
*msg_offset += sizeof(u32);
}
/**
* sep_read_msg -
* @ta_ctx: pointer to struct this_task_ctx
* @in_addr: pointer to start of parameter
* @size: size of parameter to copy (in bytes)
* @max_size: size to move up offset; SEP mesg is in word sizes
* @msg_offset: pointer to current offset (is updated)
* @byte_array: flag ti indicate whether endian must be changed
* Copies data out of the message area to caller
*/
static void sep_read_msg(struct this_task_ctx *ta_ctx, void *in_addr,
u32 size, u32 max_size, u32 *msg_offset, u32 byte_array)
{
u32 *word_ptr;
void *void_ptr;
void_ptr = ta_ctx->msgptr + *msg_offset;
word_ptr = (u32 *)void_ptr;
/* Do we need to manipulate endian? */
if (byte_array) {
u32 i;
for (i = 0; i < ((size + 3) / 4); i += 1)
*(word_ptr + i) = CHG_ENDIAN(*(word_ptr + i));
}
memcpy(in_addr, void_ptr, size);
*msg_offset += max_size;
}
/**
* sep_verify_op -
* @ta_ctx: pointer to struct this_task_ctx
* @op_code: expected op_code
* @msg_offset: pointer to current offset (is updated)
* @returns: 0 for success; error for failure
*/
static u32 sep_verify_op(struct this_task_ctx *ta_ctx, u32 op_code,
u32 *msg_offset)
{
u32 error;
u32 in_ary[2];
struct sep_device *sep = ta_ctx->sep_used;
dev_dbg(&sep->pdev->dev, "dumping return message\n");
error = sep_start_inbound_msg(ta_ctx, msg_offset);
if (error) {
dev_warn(&sep->pdev->dev,
"sep_start_inbound_msg error\n");
return error;
}
sep_read_msg(ta_ctx, in_ary, sizeof(u32) * 2, sizeof(u32) * 2,
msg_offset, 0);
if (in_ary[0] != op_code) {
dev_warn(&sep->pdev->dev,
"sep got back wrong opcode\n");
dev_warn(&sep->pdev->dev,
"got back %x; expected %x\n",
in_ary[0], op_code);
return SEP_WRONG_OPCODE;
}
if (in_ary[1] != SEP_OK) {
dev_warn(&sep->pdev->dev,
"sep execution error\n");
dev_warn(&sep->pdev->dev,
"got back %x; expected %x\n",
in_ary[1], SEP_OK);
return in_ary[0];
}
return 0;
}
/**
* sep_read_context -
* @ta_ctx: pointer to struct this_task_ctx
* @msg_offset: point to current place in SEP msg; is updated
* @dst: pointer to place to put the context
* @len: size of the context structure (differs for crypro/hash)
* This function reads the context from the msg area
* There is a special way the vendor needs to have the maximum
* length calculated so that the msg_offset is updated properly;
* it skips over some words in the msg area depending on the size
* of the context
*/
static void sep_read_context(struct this_task_ctx *ta_ctx, u32 *msg_offset,
void *dst, u32 len)
{
u32 max_length = ((len + 3) / sizeof(u32)) * sizeof(u32);
sep_read_msg(ta_ctx, dst, len, max_length, msg_offset, 0);
}
/**
* sep_write_context -
* @ta_ctx: pointer to struct this_task_ctx
* @msg_offset: point to current place in SEP msg; is updated
* @src: pointer to the current context
* @len: size of the context structure (differs for crypro/hash)
* This function writes the context to the msg area
* There is a special way the vendor needs to have the maximum
* length calculated so that the msg_offset is updated properly;
* it skips over some words in the msg area depending on the size
* of the context
*/
static void sep_write_context(struct this_task_ctx *ta_ctx, u32 *msg_offset,
void *src, u32 len)
{
u32 max_length = ((len + 3) / sizeof(u32)) * sizeof(u32);
sep_write_msg(ta_ctx, src, len, max_length, msg_offset, 0);
}
/**
* sep_clear_out -
* @ta_ctx: pointer to struct this_task_ctx
* Clear out crypto related values in sep device structure
* to enable device to be used by anyone; either kernel
* crypto or userspace app via middleware
*/
static void sep_clear_out(struct this_task_ctx *ta_ctx)
{
if (ta_ctx->src_sg_hold) {
sep_free_sg_buf(ta_ctx->src_sg_hold);
ta_ctx->src_sg_hold = NULL;
}
if (ta_ctx->dst_sg_hold) {
sep_free_sg_buf(ta_ctx->dst_sg_hold);
ta_ctx->dst_sg_hold = NULL;
}
ta_ctx->src_sg = NULL;
ta_ctx->dst_sg = NULL;
sep_free_dma_table_data_handler(ta_ctx->sep_used, &ta_ctx->dma_ctx);
if (ta_ctx->i_own_sep) {
/**
* The following unlocks the sep and makes it available
* to any other application
* First, null out crypto entries in sep before releasing it
*/
ta_ctx->sep_used->current_hash_req = NULL;
ta_ctx->sep_used->current_cypher_req = NULL;
ta_ctx->sep_used->current_request = 0;
ta_ctx->sep_used->current_hash_stage = 0;
ta_ctx->sep_used->ta_ctx = NULL;
ta_ctx->sep_used->in_kernel = 0;
ta_ctx->call_status.status = 0;
/* Remove anything confidential */
memset(ta_ctx->sep_used->shared_addr, 0,
SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES);
sep_queue_status_remove(ta_ctx->sep_used, &ta_ctx->queue_elem);
#ifdef SEP_ENABLE_RUNTIME_PM
ta_ctx->sep_used->in_use = 0;
pm_runtime_mark_last_busy(&ta_ctx->sep_used->pdev->dev);
pm_runtime_put_autosuspend(&ta_ctx->sep_used->pdev->dev);
#endif
clear_bit(SEP_WORKING_LOCK_BIT,
&ta_ctx->sep_used->in_use_flags);
ta_ctx->sep_used->pid_doing_transaction = 0;
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"[PID%d] waking up next transaction\n",
current->pid);
clear_bit(SEP_TRANSACTION_STARTED_LOCK_BIT,
&ta_ctx->sep_used->in_use_flags);
wake_up(&ta_ctx->sep_used->event_transactions);
ta_ctx->i_own_sep = 0;
}
}
/**
* Release crypto infrastructure from EINPROGRESS and
* clear sep_dev so that SEP is available to anyone
*/
static void sep_crypto_release(struct sep_system_ctx *sctx,
struct this_task_ctx *ta_ctx, u32 error)
{
struct ahash_request *hash_req = ta_ctx->current_hash_req;
struct ablkcipher_request *cypher_req =
ta_ctx->current_cypher_req;
struct sep_device *sep = ta_ctx->sep_used;
sep_clear_out(ta_ctx);
/**
* This may not yet exist depending when we
* chose to bail out. If it does exist, set
* it to 1
*/
if (ta_ctx->are_we_done_yet != NULL)
*ta_ctx->are_we_done_yet = 1;
if (cypher_req != NULL) {
if ((sctx->key_sent == 1) ||
((error != 0) && (error != -EINPROGRESS))) {
if (cypher_req->base.complete == NULL) {
dev_dbg(&sep->pdev->dev,
"release is null for cypher!");
} else {
cypher_req->base.complete(
&cypher_req->base, error);
}
}
}
if (hash_req != NULL) {
if (hash_req->base.complete == NULL) {
dev_dbg(&sep->pdev->dev,
"release is null for hash!");
} else {
hash_req->base.complete(
&hash_req->base, error);
}
}
}
/**
* This is where we grab the sep itself and tell it to do something.
* It will sleep if the sep is currently busy
* and it will return 0 if sep is now ours; error value if there
* were problems
*/
static int sep_crypto_take_sep(struct this_task_ctx *ta_ctx)
{
struct sep_device *sep = ta_ctx->sep_used;
int result;
struct sep_msgarea_hdr *my_msg_header;
my_msg_header = (struct sep_msgarea_hdr *)ta_ctx->msg;
/* add to status queue */
ta_ctx->queue_elem = sep_queue_status_add(sep, my_msg_header->opcode,
ta_ctx->nbytes, current->pid,
current->comm, sizeof(current->comm));
if (!ta_ctx->queue_elem) {
dev_dbg(&sep->pdev->dev,
"[PID%d] updating queue status error\n", current->pid);
return -EINVAL;
}
/* get the device; this can sleep */
result = sep_wait_transaction(sep);
if (result)
return result;
if (sep_dev->power_save_setup == 1)
pm_runtime_get_sync(&sep_dev->pdev->dev);
/* Copy in the message */
memcpy(sep->shared_addr, ta_ctx->msg,
SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES);
/* Copy in the dcb information if there is any */
if (ta_ctx->dcb_region) {
result = sep_activate_dcb_dmatables_context(sep,
&ta_ctx->dcb_region, &ta_ctx->dmatables_region,
ta_ctx->dma_ctx);
if (result)
return result;
}
/* Mark the device so we know how to finish the job in the tasklet */
if (ta_ctx->current_hash_req)
sep->current_hash_req = ta_ctx->current_hash_req;
else
sep->current_cypher_req = ta_ctx->current_cypher_req;
sep->current_request = ta_ctx->current_request;
sep->current_hash_stage = ta_ctx->current_hash_stage;
sep->ta_ctx = ta_ctx;
sep->in_kernel = 1;
ta_ctx->i_own_sep = 1;
/* need to set bit first to avoid race condition with interrupt */
set_bit(SEP_LEGACY_SENDMSG_DONE_OFFSET, &ta_ctx->call_status.status);
result = sep_send_command_handler(sep);
dev_dbg(&sep->pdev->dev, "[PID%d]: sending command to the sep\n",
current->pid);
if (!result)
dev_dbg(&sep->pdev->dev, "[PID%d]: command sent okay\n",
current->pid);
else {
dev_dbg(&sep->pdev->dev, "[PID%d]: cant send command\n",
current->pid);
clear_bit(SEP_LEGACY_SENDMSG_DONE_OFFSET,
&ta_ctx->call_status.status);
}
return result;
}
/**
* This function sets things up for a crypto data block process
* This does all preparation, but does not try to grab the
* sep
* @req: pointer to struct ablkcipher_request
* returns: 0 if all went well, non zero if error
*/
static int sep_crypto_block_data(struct ablkcipher_request *req)
{
int int_error;
u32 msg_offset;
static u32 msg[10];
void *src_ptr;
void *dst_ptr;
static char small_buf[100];
ssize_t copy_result;
int result;
struct scatterlist *new_sg;
struct this_task_ctx *ta_ctx;
struct crypto_ablkcipher *tfm;
struct sep_system_ctx *sctx;
struct sep_des_internal_context *des_internal;
struct sep_aes_internal_context *aes_internal;
ta_ctx = ablkcipher_request_ctx(req);
tfm = crypto_ablkcipher_reqtfm(req);
sctx = crypto_ablkcipher_ctx(tfm);
/* start the walk on scatterlists */
ablkcipher_walk_init(&ta_ctx->walk, req->src, req->dst, req->nbytes);
dev_dbg(&ta_ctx->sep_used->pdev->dev, "sep crypto block data size of %x\n",
req->nbytes);
int_error = ablkcipher_walk_phys(req, &ta_ctx->walk);
if (int_error) {
dev_warn(&ta_ctx->sep_used->pdev->dev, "walk phys error %x\n",
int_error);
return -ENOMEM;
}
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"crypto block: src is %lx dst is %lx\n",
(unsigned long)req->src, (unsigned long)req->dst);
/* Make sure all pages are even block */
int_error = sep_oddball_pages(ta_ctx->sep_used, req->src,
req->nbytes, ta_ctx->walk.blocksize, &new_sg, 1);
if (int_error < 0) {
dev_warn(&ta_ctx->sep_used->pdev->dev, "oddball page error\n");
return int_error;
} else if (int_error == 1) {
ta_ctx->src_sg = new_sg;
ta_ctx->src_sg_hold = new_sg;
} else {
ta_ctx->src_sg = req->src;
ta_ctx->src_sg_hold = NULL;
}
int_error = sep_oddball_pages(ta_ctx->sep_used, req->dst,
req->nbytes, ta_ctx->walk.blocksize, &new_sg, 0);
if (int_error < 0) {
dev_warn(&ta_ctx->sep_used->pdev->dev, "walk phys error %x\n",
int_error);
return int_error;
} else if (int_error == 1) {
ta_ctx->dst_sg = new_sg;
ta_ctx->dst_sg_hold = new_sg;
} else {
ta_ctx->dst_sg = req->dst;
ta_ctx->dst_sg_hold = NULL;
}
/* set nbytes for queue status */
ta_ctx->nbytes = req->nbytes;
/* Key already done; this is for data */
dev_dbg(&ta_ctx->sep_used->pdev->dev, "sending data\n");
/* check for valid data and proper spacing */
src_ptr = sg_virt(ta_ctx->src_sg);
dst_ptr = sg_virt(ta_ctx->dst_sg);
if (!src_ptr || !dst_ptr ||
(ta_ctx->current_cypher_req->nbytes %
crypto_ablkcipher_blocksize(tfm))) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"cipher block size odd\n");
dev_warn(&ta_ctx->sep_used->pdev->dev,
"cipher block size is %x\n",
crypto_ablkcipher_blocksize(tfm));
dev_warn(&ta_ctx->sep_used->pdev->dev,
"cipher data size is %x\n",
ta_ctx->current_cypher_req->nbytes);
return -EINVAL;
}
if (partial_overlap(src_ptr, dst_ptr,
ta_ctx->current_cypher_req->nbytes)) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"block partial overlap\n");
return -EINVAL;
}
/* Put together the message */
sep_make_header(ta_ctx, &msg_offset, ta_ctx->block_opcode);
/* If des, and size is 1 block, put directly in msg */
if ((ta_ctx->block_opcode == SEP_DES_BLOCK_OPCODE) &&
(req->nbytes == crypto_ablkcipher_blocksize(tfm))) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"writing out one block des\n");
copy_result = sg_copy_to_buffer(
ta_ctx->src_sg, sep_sg_nents(ta_ctx->src_sg),
small_buf, crypto_ablkcipher_blocksize(tfm));
if (copy_result != crypto_ablkcipher_blocksize(tfm)) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"des block copy failed\n");
return -ENOMEM;
}
/* Put data into message */
sep_write_msg(ta_ctx, small_buf,
crypto_ablkcipher_blocksize(tfm),
crypto_ablkcipher_blocksize(tfm) * 2,
&msg_offset, 1);
/* Put size into message */
sep_write_msg(ta_ctx, &req->nbytes,
sizeof(u32), sizeof(u32), &msg_offset, 0);
} else {
/* Otherwise, fill out dma tables */
ta_ctx->dcb_input_data.app_in_address = src_ptr;
ta_ctx->dcb_input_data.data_in_size = req->nbytes;
ta_ctx->dcb_input_data.app_out_address = dst_ptr;
ta_ctx->dcb_input_data.block_size =
crypto_ablkcipher_blocksize(tfm);
ta_ctx->dcb_input_data.tail_block_size = 0;
ta_ctx->dcb_input_data.is_applet = 0;
ta_ctx->dcb_input_data.src_sg = ta_ctx->src_sg;
ta_ctx->dcb_input_data.dst_sg = ta_ctx->dst_sg;
result = sep_create_dcb_dmatables_context_kernel(
ta_ctx->sep_used,
&ta_ctx->dcb_region,
&ta_ctx->dmatables_region,
&ta_ctx->dma_ctx,
&ta_ctx->dcb_input_data,
1);
if (result) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"crypto dma table create failed\n");
return -EINVAL;
}
/* Portion of msg is nulled (no data) */
msg[0] = (u32)0;
msg[1] = (u32)0;
msg[2] = (u32)0;
msg[3] = (u32)0;
msg[4] = (u32)0;
sep_write_msg(ta_ctx, (void *)msg, sizeof(u32) * 5,
sizeof(u32) * 5, &msg_offset, 0);
}
/**
* Before we write the message, we need to overwrite the
* vendor's IV with the one from our own ablkcipher walk
* iv because this is needed for dm-crypt
*/
sep_dump_ivs(req, "sending data block to sep\n");
if ((ta_ctx->current_request == DES_CBC) &&
(ta_ctx->des_opmode == SEP_DES_CBC)) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"overwrite vendor iv on DES\n");
des_internal = (struct sep_des_internal_context *)
sctx->des_private_ctx.ctx_buf;
memcpy((void *)des_internal->iv_context,
ta_ctx->walk.iv, crypto_ablkcipher_ivsize(tfm));
} else if ((ta_ctx->current_request == AES_CBC) &&
(ta_ctx->aes_opmode == SEP_AES_CBC)) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"overwrite vendor iv on AES\n");
aes_internal = (struct sep_aes_internal_context *)
sctx->aes_private_ctx.cbuff;
memcpy((void *)aes_internal->aes_ctx_iv,
ta_ctx->walk.iv, crypto_ablkcipher_ivsize(tfm));
}
/* Write context into message */
if (ta_ctx->block_opcode == SEP_DES_BLOCK_OPCODE) {
sep_write_context(ta_ctx, &msg_offset,
&sctx->des_private_ctx,
sizeof(struct sep_des_private_context));
} else {
sep_write_context(ta_ctx, &msg_offset,
&sctx->aes_private_ctx,
sizeof(struct sep_aes_private_context));
}
/* conclude message */
sep_end_msg(ta_ctx, msg_offset);
/* Parent (caller) is now ready to tell the sep to do ahead */
return 0;
}
/**
* This function sets things up for a crypto key submit process
* This does all preparation, but does not try to grab the
* sep
* @req: pointer to struct ablkcipher_request
* returns: 0 if all went well, non zero if error
*/
static int sep_crypto_send_key(struct ablkcipher_request *req)
{
int int_error;
u32 msg_offset;
static u32 msg[10];
u32 max_length;
struct this_task_ctx *ta_ctx;
struct crypto_ablkcipher *tfm;
struct sep_system_ctx *sctx;
ta_ctx = ablkcipher_request_ctx(req);
tfm = crypto_ablkcipher_reqtfm(req);
sctx = crypto_ablkcipher_ctx(tfm);
dev_dbg(&ta_ctx->sep_used->pdev->dev, "sending key\n");
/* start the walk on scatterlists */
ablkcipher_walk_init(&ta_ctx->walk, req->src, req->dst, req->nbytes);
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"sep crypto block data size of %x\n", req->nbytes);
int_error = ablkcipher_walk_phys(req, &ta_ctx->walk);
if (int_error) {
dev_warn(&ta_ctx->sep_used->pdev->dev, "walk phys error %x\n",
int_error);
return -ENOMEM;
}
/* check iv */
if ((ta_ctx->current_request == DES_CBC) &&
(ta_ctx->des_opmode == SEP_DES_CBC)) {
if (!ta_ctx->walk.iv) {
dev_warn(&ta_ctx->sep_used->pdev->dev, "no iv found\n");
return -EINVAL;
}
memcpy(ta_ctx->iv, ta_ctx->walk.iv, SEP_DES_IV_SIZE_BYTES);
}
if ((ta_ctx->current_request == AES_CBC) &&
(ta_ctx->aes_opmode == SEP_AES_CBC)) {
if (!ta_ctx->walk.iv) {
dev_warn(&ta_ctx->sep_used->pdev->dev, "no iv found\n");
return -EINVAL;
}
memcpy(ta_ctx->iv, ta_ctx->walk.iv, SEP_AES_IV_SIZE_BYTES);
}
/* put together message to SEP */
/* Start with op code */
sep_make_header(ta_ctx, &msg_offset, ta_ctx->init_opcode);
/* now deal with IV */
if (ta_ctx->init_opcode == SEP_DES_INIT_OPCODE) {
if (ta_ctx->des_opmode == SEP_DES_CBC) {
sep_write_msg(ta_ctx, ta_ctx->iv,
SEP_DES_IV_SIZE_BYTES, sizeof(u32) * 4,
&msg_offset, 1);
} else {
/* Skip if ECB */
msg_offset += 4 * sizeof(u32);
}
} else {
max_length = ((SEP_AES_IV_SIZE_BYTES + 3) /
sizeof(u32)) * sizeof(u32);
if (ta_ctx->aes_opmode == SEP_AES_CBC) {
sep_write_msg(ta_ctx, ta_ctx->iv,
SEP_AES_IV_SIZE_BYTES, max_length,
&msg_offset, 1);
} else {
/* Skip if ECB */
msg_offset += max_length;
}
}
/* load the key */
if (ta_ctx->init_opcode == SEP_DES_INIT_OPCODE) {
sep_write_msg(ta_ctx, (void *)&sctx->key.des.key1,
sizeof(u32) * 8, sizeof(u32) * 8,
&msg_offset, 1);
msg[0] = (u32)sctx->des_nbr_keys;
msg[1] = (u32)ta_ctx->des_encmode;
msg[2] = (u32)ta_ctx->des_opmode;
sep_write_msg(ta_ctx, (void *)msg,
sizeof(u32) * 3, sizeof(u32) * 3,
&msg_offset, 0);
} else {
sep_write_msg(ta_ctx, (void *)&sctx->key.aes,
sctx->keylen,
SEP_AES_MAX_KEY_SIZE_BYTES,
&msg_offset, 1);
msg[0] = (u32)sctx->aes_key_size;
msg[1] = (u32)ta_ctx->aes_encmode;
msg[2] = (u32)ta_ctx->aes_opmode;
msg[3] = (u32)0; /* Secret key is not used */
sep_write_msg(ta_ctx, (void *)msg,
sizeof(u32) * 4, sizeof(u32) * 4,
&msg_offset, 0);
}
/* conclude message */
sep_end_msg(ta_ctx, msg_offset);
/* Parent (caller) is now ready to tell the sep to do ahead */
return 0;
}
/* This needs to be run as a work queue as it can be put asleep */
static void sep_crypto_block(void *data)
{
unsigned long end_time;
int result;
struct ablkcipher_request *req;
struct this_task_ctx *ta_ctx;
struct crypto_ablkcipher *tfm;
struct sep_system_ctx *sctx;
int are_we_done_yet;
req = (struct ablkcipher_request *)data;
ta_ctx = ablkcipher_request_ctx(req);
tfm = crypto_ablkcipher_reqtfm(req);
sctx = crypto_ablkcipher_ctx(tfm);
ta_ctx->are_we_done_yet = &are_we_done_yet;
pr_debug("sep_crypto_block\n");
pr_debug("tfm is %p sctx is %p ta_ctx is %p\n",
tfm, sctx, ta_ctx);
pr_debug("key_sent is %d\n", sctx->key_sent);
/* do we need to send the key */
if (sctx->key_sent == 0) {
are_we_done_yet = 0;
result = sep_crypto_send_key(req); /* prep to send key */
if (result != 0) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"could not prep key %x\n", result);
sep_crypto_release(sctx, ta_ctx, result);
return;
}
result = sep_crypto_take_sep(ta_ctx);
if (result) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"sep_crypto_take_sep for key send failed\n");
sep_crypto_release(sctx, ta_ctx, result);
return;
}
/* now we sit and wait up to a fixed time for completion */
end_time = jiffies + (WAIT_TIME * HZ);
while ((time_before(jiffies, end_time)) &&
(are_we_done_yet == 0))
schedule();
/* Done waiting; still not done yet? */
if (are_we_done_yet == 0) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"Send key job never got done\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
return;
}
/* Set the key sent variable so this can be skipped later */
sctx->key_sent = 1;
}
/* Key sent (or maybe not if we did not have to), now send block */
are_we_done_yet = 0;
result = sep_crypto_block_data(req);
if (result != 0) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"could prep not send block %x\n", result);
sep_crypto_release(sctx, ta_ctx, result);
return;
}
result = sep_crypto_take_sep(ta_ctx);
if (result) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"sep_crypto_take_sep for block send failed\n");
sep_crypto_release(sctx, ta_ctx, result);
return;
}
/* now we sit and wait up to a fixed time for completion */
end_time = jiffies + (WAIT_TIME * HZ);
while ((time_before(jiffies, end_time)) && (are_we_done_yet == 0))
schedule();
/* Done waiting; still not done yet? */
if (are_we_done_yet == 0) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"Send block job never got done\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
return;
}
/* That's it; entire thing done, get out of queue */
pr_debug("crypto_block leaving\n");
pr_debug("tfm is %p sctx is %p ta_ctx is %p\n", tfm, sctx, ta_ctx);
}
/**
* Post operation (after interrupt) for crypto block
*/
static u32 crypto_post_op(struct sep_device *sep)
{
/* HERE */
u32 u32_error;
u32 msg_offset;
ssize_t copy_result;
static char small_buf[100];
struct ablkcipher_request *req;
struct this_task_ctx *ta_ctx;
struct sep_system_ctx *sctx;
struct crypto_ablkcipher *tfm;
struct sep_des_internal_context *des_internal;
struct sep_aes_internal_context *aes_internal;
if (!sep->current_cypher_req)
return -EINVAL;
/* hold req since we need to submit work after clearing sep */
req = sep->current_cypher_req;
ta_ctx = ablkcipher_request_ctx(sep->current_cypher_req);
tfm = crypto_ablkcipher_reqtfm(sep->current_cypher_req);
sctx = crypto_ablkcipher_ctx(tfm);
pr_debug("crypto_post op\n");
pr_debug("key_sent is %d tfm is %p sctx is %p ta_ctx is %p\n",
sctx->key_sent, tfm, sctx, ta_ctx);
dev_dbg(&ta_ctx->sep_used->pdev->dev, "crypto post_op\n");
dev_dbg(&ta_ctx->sep_used->pdev->dev, "crypto post_op message dump\n");
/* first bring msg from shared area to local area */
memcpy(ta_ctx->msg, sep->shared_addr,
SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES);
/* Is this the result of performing init (key to SEP */
if (sctx->key_sent == 0) {
/* Did SEP do it okay */
u32_error = sep_verify_op(ta_ctx, ta_ctx->init_opcode,
&msg_offset);
if (u32_error) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"aes init error %x\n", u32_error);
sep_crypto_release(sctx, ta_ctx, u32_error);
return u32_error;
}
/* Read Context */
if (ta_ctx->init_opcode == SEP_DES_INIT_OPCODE) {
sep_read_context(ta_ctx, &msg_offset,
&sctx->des_private_ctx,
sizeof(struct sep_des_private_context));
} else {
sep_read_context(ta_ctx, &msg_offset,
&sctx->aes_private_ctx,
sizeof(struct sep_aes_private_context));
}
sep_dump_ivs(req, "after sending key to sep\n");
/* key sent went okay; release sep, and set are_we_done_yet */
sctx->key_sent = 1;
sep_crypto_release(sctx, ta_ctx, -EINPROGRESS);
} else {
/**
* This is the result of a block request
*/
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"crypto_post_op block response\n");
u32_error = sep_verify_op(ta_ctx, ta_ctx->block_opcode,
&msg_offset);
if (u32_error) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"sep block error %x\n", u32_error);
sep_crypto_release(sctx, ta_ctx, u32_error);
return -EINVAL;
}
if (ta_ctx->block_opcode == SEP_DES_BLOCK_OPCODE) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"post op for DES\n");
/* special case for 1 block des */
if (sep->current_cypher_req->nbytes ==
crypto_ablkcipher_blocksize(tfm)) {
sep_read_msg(ta_ctx, small_buf,
crypto_ablkcipher_blocksize(tfm),
crypto_ablkcipher_blocksize(tfm) * 2,
&msg_offset, 1);
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"reading in block des\n");
copy_result = sg_copy_from_buffer(
ta_ctx->dst_sg,
sep_sg_nents(ta_ctx->dst_sg),
small_buf,
crypto_ablkcipher_blocksize(tfm));
if (copy_result !=
crypto_ablkcipher_blocksize(tfm)) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"des block copy failed\n");
sep_crypto_release(sctx, ta_ctx,
-ENOMEM);
return -ENOMEM;
}
}
/* Read Context */
sep_read_context(ta_ctx, &msg_offset,
&sctx->des_private_ctx,
sizeof(struct sep_des_private_context));
} else {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"post op for AES\n");
/* Skip the MAC Output */
msg_offset += (sizeof(u32) * 4);
/* Read Context */
sep_read_context(ta_ctx, &msg_offset,
&sctx->aes_private_ctx,
sizeof(struct sep_aes_private_context));
}
/* Copy to correct sg if this block had oddball pages */
if (ta_ctx->dst_sg_hold)
sep_copy_sg(ta_ctx->sep_used,
ta_ctx->dst_sg,
ta_ctx->current_cypher_req->dst,
ta_ctx->current_cypher_req->nbytes);
/**
* Copy the iv's back to the walk.iv
* This is required for dm_crypt
*/
sep_dump_ivs(req, "got data block from sep\n");
if ((ta_ctx->current_request == DES_CBC) &&
(ta_ctx->des_opmode == SEP_DES_CBC)) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"returning result iv to walk on DES\n");
des_internal = (struct sep_des_internal_context *)
sctx->des_private_ctx.ctx_buf;
memcpy(ta_ctx->walk.iv,
(void *)des_internal->iv_context,
crypto_ablkcipher_ivsize(tfm));
} else if ((ta_ctx->current_request == AES_CBC) &&
(ta_ctx->aes_opmode == SEP_AES_CBC)) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"returning result iv to walk on AES\n");
aes_internal = (struct sep_aes_internal_context *)
sctx->aes_private_ctx.cbuff;
memcpy(ta_ctx->walk.iv,
(void *)aes_internal->aes_ctx_iv,
crypto_ablkcipher_ivsize(tfm));
}
/* finished, release everything */
sep_crypto_release(sctx, ta_ctx, 0);
}
pr_debug("crypto_post_op done\n");
pr_debug("key_sent is %d tfm is %p sctx is %p ta_ctx is %p\n",
sctx->key_sent, tfm, sctx, ta_ctx);
return 0;
}
static u32 hash_init_post_op(struct sep_device *sep)
{
u32 u32_error;
u32 msg_offset;
struct crypto_ahash *tfm = crypto_ahash_reqtfm(sep->current_hash_req);
struct this_task_ctx *ta_ctx = ahash_request_ctx(sep->current_hash_req);
struct sep_system_ctx *sctx = crypto_ahash_ctx(tfm);
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"hash init post op\n");
/* first bring msg from shared area to local area */
memcpy(ta_ctx->msg, sep->shared_addr,
SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES);
u32_error = sep_verify_op(ta_ctx, SEP_HASH_INIT_OPCODE,
&msg_offset);
if (u32_error) {
dev_warn(&ta_ctx->sep_used->pdev->dev, "hash init error %x\n",
u32_error);
sep_crypto_release(sctx, ta_ctx, u32_error);
return u32_error;
}
/* Read Context */
sep_read_context(ta_ctx, &msg_offset,
&sctx->hash_private_ctx,
sizeof(struct sep_hash_private_context));
/* Signal to crypto infrastructure and clear out */
dev_dbg(&ta_ctx->sep_used->pdev->dev, "hash init post op done\n");
sep_crypto_release(sctx, ta_ctx, 0);
return 0;
}
static u32 hash_update_post_op(struct sep_device *sep)
{
u32 u32_error;
u32 msg_offset;
struct crypto_ahash *tfm = crypto_ahash_reqtfm(sep->current_hash_req);
struct this_task_ctx *ta_ctx = ahash_request_ctx(sep->current_hash_req);
struct sep_system_ctx *sctx = crypto_ahash_ctx(tfm);
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"hash update post op\n");
/* first bring msg from shared area to local area */
memcpy(ta_ctx->msg, sep->shared_addr,
SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES);
u32_error = sep_verify_op(ta_ctx, SEP_HASH_UPDATE_OPCODE,
&msg_offset);
if (u32_error) {
dev_warn(&ta_ctx->sep_used->pdev->dev, "hash init error %x\n",
u32_error);
sep_crypto_release(sctx, ta_ctx, u32_error);
return u32_error;
}
/* Read Context */
sep_read_context(ta_ctx, &msg_offset,
&sctx->hash_private_ctx,
sizeof(struct sep_hash_private_context));
/**
* Following is only for finup; if we just completed the
* data portion of finup, we now need to kick off the
* finish portion of finup.
*/
if (ta_ctx->sep_used->current_hash_stage == HASH_FINUP_DATA) {
/* first reset stage to HASH_FINUP_FINISH */
ta_ctx->sep_used->current_hash_stage = HASH_FINUP_FINISH;
/* now enqueue the finish operation */
spin_lock_irq(&queue_lock);
u32_error = crypto_enqueue_request(&sep_queue,
&ta_ctx->sep_used->current_hash_req->base);
spin_unlock_irq(&queue_lock);
if ((u32_error != 0) && (u32_error != -EINPROGRESS)) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"spe cypher post op cant queue\n");
sep_crypto_release(sctx, ta_ctx, u32_error);
return u32_error;
}
/* schedule the data send */
u32_error = sep_submit_work(ta_ctx->sep_used->workqueue,
sep_dequeuer, (void *)&sep_queue);
if (u32_error) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"cant submit work sep_crypto_block\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
return -EINVAL;
}
}
/* Signal to crypto infrastructure and clear out */
dev_dbg(&ta_ctx->sep_used->pdev->dev, "hash update post op done\n");
sep_crypto_release(sctx, ta_ctx, 0);
return 0;
}
static u32 hash_final_post_op(struct sep_device *sep)
{
int max_length;
u32 u32_error;
u32 msg_offset;
struct crypto_ahash *tfm = crypto_ahash_reqtfm(sep->current_hash_req);
struct sep_system_ctx *sctx = crypto_ahash_ctx(tfm);
struct this_task_ctx *ta_ctx = ahash_request_ctx(sep->current_hash_req);
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"hash final post op\n");
/* first bring msg from shared area to local area */
memcpy(ta_ctx->msg, sep->shared_addr,
SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES);
u32_error = sep_verify_op(ta_ctx, SEP_HASH_FINISH_OPCODE,
&msg_offset);
if (u32_error) {
dev_warn(&ta_ctx->sep_used->pdev->dev, "hash finish error %x\n",
u32_error);
sep_crypto_release(sctx, ta_ctx, u32_error);
return u32_error;
}
/* Grab the result */
if (ta_ctx->current_hash_req->result == NULL) {
/* Oops, null buffer; error out here */
dev_warn(&ta_ctx->sep_used->pdev->dev,
"hash finish null buffer\n");
sep_crypto_release(sctx, ta_ctx, (u32)-ENOMEM);
return -ENOMEM;
}
max_length = (((SEP_HASH_RESULT_SIZE_WORDS * sizeof(u32)) + 3) /
sizeof(u32)) * sizeof(u32);
sep_read_msg(ta_ctx,
ta_ctx->current_hash_req->result,
crypto_ahash_digestsize(tfm), max_length,
&msg_offset, 0);
/* Signal to crypto infrastructure and clear out */
dev_dbg(&ta_ctx->sep_used->pdev->dev, "hash finish post op done\n");
sep_crypto_release(sctx, ta_ctx, 0);
return 0;
}
static u32 hash_digest_post_op(struct sep_device *sep)
{
int max_length;
u32 u32_error;
u32 msg_offset;
struct crypto_ahash *tfm = crypto_ahash_reqtfm(sep->current_hash_req);
struct sep_system_ctx *sctx = crypto_ahash_ctx(tfm);
struct this_task_ctx *ta_ctx = ahash_request_ctx(sep->current_hash_req);
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"hash digest post op\n");
/* first bring msg from shared area to local area */
memcpy(ta_ctx->msg, sep->shared_addr,
SEP_DRIVER_MESSAGE_SHARED_AREA_SIZE_IN_BYTES);
u32_error = sep_verify_op(ta_ctx, SEP_HASH_SINGLE_OPCODE,
&msg_offset);
if (u32_error) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"hash digest finish error %x\n", u32_error);
sep_crypto_release(sctx, ta_ctx, u32_error);
return u32_error;
}
/* Grab the result */
if (ta_ctx->current_hash_req->result == NULL) {
/* Oops, null buffer; error out here */
dev_warn(&ta_ctx->sep_used->pdev->dev,
"hash digest finish null buffer\n");
sep_crypto_release(sctx, ta_ctx, (u32)-ENOMEM);
return -ENOMEM;
}
max_length = (((SEP_HASH_RESULT_SIZE_WORDS * sizeof(u32)) + 3) /
sizeof(u32)) * sizeof(u32);
sep_read_msg(ta_ctx,
ta_ctx->current_hash_req->result,
crypto_ahash_digestsize(tfm), max_length,
&msg_offset, 0);
/* Signal to crypto infrastructure and clear out */
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"hash digest finish post op done\n");
sep_crypto_release(sctx, ta_ctx, 0);
return 0;
}
/**
* The sep_finish function is the function that is scheduled (via tasklet)
* by the interrupt service routine when the SEP sends and interrupt
* This is only called by the interrupt handler as a tasklet.
*/
static void sep_finish(unsigned long data)
{
struct sep_device *sep_dev;
int res;
res = 0;
if (data == 0) {
pr_debug("sep_finish called with null data\n");
return;
}
sep_dev = (struct sep_device *)data;
if (sep_dev == NULL) {
pr_debug("sep_finish; sep_dev is NULL\n");
return;
}
if (sep_dev->in_kernel == (u32)0) {
dev_warn(&sep_dev->pdev->dev,
"sep_finish; not in kernel operation\n");
return;
}
/* Did we really do a sep command prior to this? */
if (0 == test_bit(SEP_LEGACY_SENDMSG_DONE_OFFSET,
&sep_dev->ta_ctx->call_status.status)) {
dev_warn(&sep_dev->pdev->dev, "[PID%d] sendmsg not called\n",
current->pid);
return;
}
if (sep_dev->send_ct != sep_dev->reply_ct) {
dev_warn(&sep_dev->pdev->dev,
"[PID%d] poll; no message came back\n",
current->pid);
return;
}
/* Check for error (In case time ran out) */
if ((res != 0x0) && (res != 0x8)) {
dev_warn(&sep_dev->pdev->dev,
"[PID%d] poll; poll error GPR3 is %x\n",
current->pid, res);
return;
}
/* What kind of interrupt from sep was this? */
res = sep_read_reg(sep_dev, HW_HOST_SEP_HOST_GPR2_REG_ADDR);
dev_dbg(&sep_dev->pdev->dev, "[PID%d] GPR2 at crypto finish is %x\n",
current->pid, res);
/* Print request? */
if ((res >> 30) & 0x1) {
dev_dbg(&sep_dev->pdev->dev, "[PID%d] sep print req\n",
current->pid);
dev_dbg(&sep_dev->pdev->dev, "[PID%d] contents: %s\n",
current->pid,
(char *)(sep_dev->shared_addr +
SEP_DRIVER_PRINTF_OFFSET_IN_BYTES));
return;
}
/* Request for daemon (not currently in POR)? */
if (res >> 31) {
dev_dbg(&sep_dev->pdev->dev,
"[PID%d] sep request; ignoring\n",
current->pid);
return;
}
/* If we got here, then we have a replay to a sep command */
dev_dbg(&sep_dev->pdev->dev,
"[PID%d] sep reply to command; processing request: %x\n",
current->pid, sep_dev->current_request);
switch (sep_dev->current_request) {
case AES_CBC:
case AES_ECB:
case DES_CBC:
case DES_ECB:
res = crypto_post_op(sep_dev);
break;
case SHA1:
case MD5:
case SHA224:
case SHA256:
switch (sep_dev->current_hash_stage) {
case HASH_INIT:
res = hash_init_post_op(sep_dev);
break;
case HASH_UPDATE:
case HASH_FINUP_DATA:
res = hash_update_post_op(sep_dev);
break;
case HASH_FINUP_FINISH:
case HASH_FINISH:
res = hash_final_post_op(sep_dev);
break;
case HASH_DIGEST:
res = hash_digest_post_op(sep_dev);
break;
default:
pr_debug("sep - invalid stage for hash finish\n");
}
break;
default:
pr_debug("sep - invalid request for finish\n");
}
if (res)
pr_debug("sep - finish returned error %x\n", res);
}
static int sep_hash_cra_init(struct crypto_tfm *tfm)
{
const char *alg_name = crypto_tfm_alg_name(tfm);
pr_debug("sep_hash_cra_init name is %s\n", alg_name);
crypto_ahash_set_reqsize(__crypto_ahash_cast(tfm),
sizeof(struct this_task_ctx));
return 0;
}
static void sep_hash_cra_exit(struct crypto_tfm *tfm)
{
pr_debug("sep_hash_cra_exit\n");
}
static void sep_hash_init(void *data)
{
u32 msg_offset;
int result;
struct ahash_request *req;
struct crypto_ahash *tfm;
struct this_task_ctx *ta_ctx;
struct sep_system_ctx *sctx;
unsigned long end_time;
int are_we_done_yet;
req = (struct ahash_request *)data;
tfm = crypto_ahash_reqtfm(req);
sctx = crypto_ahash_ctx(tfm);
ta_ctx = ahash_request_ctx(req);
ta_ctx->sep_used = sep_dev;
ta_ctx->are_we_done_yet = &are_we_done_yet;
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"sep_hash_init\n");
ta_ctx->current_hash_stage = HASH_INIT;
/* opcode and mode */
sep_make_header(ta_ctx, &msg_offset, SEP_HASH_INIT_OPCODE);
sep_write_msg(ta_ctx, &ta_ctx->hash_opmode,
sizeof(u32), sizeof(u32), &msg_offset, 0);
sep_end_msg(ta_ctx, msg_offset);
are_we_done_yet = 0;
result = sep_crypto_take_sep(ta_ctx);
if (result) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"sep_hash_init take sep failed\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
}
/* now we sit and wait up to a fixed time for completion */
end_time = jiffies + (WAIT_TIME * HZ);
while ((time_before(jiffies, end_time)) && (are_we_done_yet == 0))
schedule();
/* Done waiting; still not done yet? */
if (are_we_done_yet == 0) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"hash init never got done\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
return;
}
}
static void sep_hash_update(void *data)
{
int int_error;
u32 msg_offset;
u32 len;
struct sep_hash_internal_context *int_ctx;
u32 block_size;
u32 head_len;
u32 tail_len;
int are_we_done_yet;
static u32 msg[10];
static char small_buf[100];
void *src_ptr;
struct scatterlist *new_sg;
ssize_t copy_result;
struct ahash_request *req;
struct crypto_ahash *tfm;
struct this_task_ctx *ta_ctx;
struct sep_system_ctx *sctx;
unsigned long end_time;
req = (struct ahash_request *)data;
tfm = crypto_ahash_reqtfm(req);
sctx = crypto_ahash_ctx(tfm);
ta_ctx = ahash_request_ctx(req);
ta_ctx->sep_used = sep_dev;
ta_ctx->are_we_done_yet = &are_we_done_yet;
/* length for queue status */
ta_ctx->nbytes = req->nbytes;
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"sep_hash_update\n");
ta_ctx->current_hash_stage = HASH_UPDATE;
len = req->nbytes;
block_size = crypto_tfm_alg_blocksize(crypto_ahash_tfm(tfm));
tail_len = req->nbytes % block_size;
dev_dbg(&ta_ctx->sep_used->pdev->dev, "length is %x\n", len);
dev_dbg(&ta_ctx->sep_used->pdev->dev, "block_size is %x\n", block_size);
dev_dbg(&ta_ctx->sep_used->pdev->dev, "tail len is %x\n", tail_len);
/* Compute header/tail sizes */
int_ctx = (struct sep_hash_internal_context *)&sctx->
hash_private_ctx.internal_context;
head_len = (block_size - int_ctx->prev_update_bytes) % block_size;
tail_len = (req->nbytes - head_len) % block_size;
/* Make sure all pages are an even block */
int_error = sep_oddball_pages(ta_ctx->sep_used, req->src,
req->nbytes,
block_size, &new_sg, 1);
if (int_error < 0) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"oddball pages error in crash update\n");
sep_crypto_release(sctx, ta_ctx, -ENOMEM);
return;
} else if (int_error == 1) {
ta_ctx->src_sg = new_sg;
ta_ctx->src_sg_hold = new_sg;
} else {
ta_ctx->src_sg = req->src;
ta_ctx->src_sg_hold = NULL;
}
src_ptr = sg_virt(ta_ctx->src_sg);
if ((!req->nbytes) || (!ta_ctx->src_sg)) {
/* null data */
src_ptr = NULL;
}
ta_ctx->dcb_input_data.app_in_address = src_ptr;
ta_ctx->dcb_input_data.data_in_size =
req->nbytes - (head_len + tail_len);
ta_ctx->dcb_input_data.app_out_address = NULL;
ta_ctx->dcb_input_data.block_size = block_size;
ta_ctx->dcb_input_data.tail_block_size = 0;
ta_ctx->dcb_input_data.is_applet = 0;
ta_ctx->dcb_input_data.src_sg = ta_ctx->src_sg;
ta_ctx->dcb_input_data.dst_sg = NULL;
int_error = sep_create_dcb_dmatables_context_kernel(
ta_ctx->sep_used,
&ta_ctx->dcb_region,
&ta_ctx->dmatables_region,
&ta_ctx->dma_ctx,
&ta_ctx->dcb_input_data,
1);
if (int_error) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"hash update dma table create failed\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
return;
}
/* Construct message to SEP */
sep_make_header(ta_ctx, &msg_offset, SEP_HASH_UPDATE_OPCODE);
msg[0] = (u32)0;
msg[1] = (u32)0;
msg[2] = (u32)0;
sep_write_msg(ta_ctx, msg, sizeof(u32) * 3, sizeof(u32) * 3,
&msg_offset, 0);
/* Handle remainders */
/* Head */
sep_write_msg(ta_ctx, &head_len, sizeof(u32),
sizeof(u32), &msg_offset, 0);
if (head_len) {
copy_result = sg_copy_to_buffer(
req->src,
sep_sg_nents(ta_ctx->src_sg),
small_buf, head_len);
if (copy_result != head_len) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"sg head copy failure in hash block\n");
sep_crypto_release(sctx, ta_ctx, -ENOMEM);
return;
}
sep_write_msg(ta_ctx, small_buf, head_len,
sizeof(u32) * 32, &msg_offset, 1);
} else {
msg_offset += sizeof(u32) * 32;
}
/* Tail */
sep_write_msg(ta_ctx, &tail_len, sizeof(u32),
sizeof(u32), &msg_offset, 0);
if (tail_len) {
copy_result = sep_copy_offset_sg(
ta_ctx->sep_used,
ta_ctx->src_sg,
req->nbytes - tail_len,
small_buf, tail_len);
if (copy_result != tail_len) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"sg tail copy failure in hash block\n");
sep_crypto_release(sctx, ta_ctx, -ENOMEM);
return;
}
sep_write_msg(ta_ctx, small_buf, tail_len,
sizeof(u32) * 32, &msg_offset, 1);
} else {
msg_offset += sizeof(u32) * 32;
}
/* Context */
sep_write_context(ta_ctx, &msg_offset, &sctx->hash_private_ctx,
sizeof(struct sep_hash_private_context));
sep_end_msg(ta_ctx, msg_offset);
are_we_done_yet = 0;
int_error = sep_crypto_take_sep(ta_ctx);
if (int_error) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"sep_hash_update take sep failed\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
}
/* now we sit and wait up to a fixed time for completion */
end_time = jiffies + (WAIT_TIME * HZ);
while ((time_before(jiffies, end_time)) && (are_we_done_yet == 0))
schedule();
/* Done waiting; still not done yet? */
if (are_we_done_yet == 0) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"hash update never got done\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
return;
}
}
static void sep_hash_final(void *data)
{
u32 msg_offset;
struct ahash_request *req;
struct crypto_ahash *tfm;
struct this_task_ctx *ta_ctx;
struct sep_system_ctx *sctx;
int result;
unsigned long end_time;
int are_we_done_yet;
req = (struct ahash_request *)data;
tfm = crypto_ahash_reqtfm(req);
sctx = crypto_ahash_ctx(tfm);
ta_ctx = ahash_request_ctx(req);
ta_ctx->sep_used = sep_dev;
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"sep_hash_final\n");
ta_ctx->current_hash_stage = HASH_FINISH;
ta_ctx->are_we_done_yet = &are_we_done_yet;
/* opcode and mode */
sep_make_header(ta_ctx, &msg_offset, SEP_HASH_FINISH_OPCODE);
/* Context */
sep_write_context(ta_ctx, &msg_offset, &sctx->hash_private_ctx,
sizeof(struct sep_hash_private_context));
sep_end_msg(ta_ctx, msg_offset);
are_we_done_yet = 0;
result = sep_crypto_take_sep(ta_ctx);
if (result) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"sep_hash_final take sep failed\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
}
/* now we sit and wait up to a fixed time for completion */
end_time = jiffies + (WAIT_TIME * HZ);
while ((time_before(jiffies, end_time)) && (are_we_done_yet == 0))
schedule();
/* Done waiting; still not done yet? */
if (are_we_done_yet == 0) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"hash final job never got done\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
return;
}
}
static void sep_hash_digest(void *data)
{
int int_error;
u32 msg_offset;
u32 block_size;
u32 msg[10];
size_t copy_result;
int result;
int are_we_done_yet;
u32 tail_len;
static char small_buf[100];
struct scatterlist *new_sg;
void *src_ptr;
struct ahash_request *req;
struct crypto_ahash *tfm;
struct this_task_ctx *ta_ctx;
struct sep_system_ctx *sctx;
unsigned long end_time;
req = (struct ahash_request *)data;
tfm = crypto_ahash_reqtfm(req);
sctx = crypto_ahash_ctx(tfm);
ta_ctx = ahash_request_ctx(req);
ta_ctx->sep_used = sep_dev;
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"sep_hash_digest\n");
ta_ctx->current_hash_stage = HASH_DIGEST;
ta_ctx->are_we_done_yet = &are_we_done_yet;
/* length for queue status */
ta_ctx->nbytes = req->nbytes;
block_size = crypto_tfm_alg_blocksize(crypto_ahash_tfm(tfm));
tail_len = req->nbytes % block_size;
dev_dbg(&ta_ctx->sep_used->pdev->dev, "length is %x\n", req->nbytes);
dev_dbg(&ta_ctx->sep_used->pdev->dev, "block_size is %x\n", block_size);
dev_dbg(&ta_ctx->sep_used->pdev->dev, "tail len is %x\n", tail_len);
/* Make sure all pages are an even block */
int_error = sep_oddball_pages(ta_ctx->sep_used, req->src,
req->nbytes,
block_size, &new_sg, 1);
if (int_error < 0) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"oddball pages error in crash update\n");
sep_crypto_release(sctx, ta_ctx, -ENOMEM);
return;
} else if (int_error == 1) {
ta_ctx->src_sg = new_sg;
ta_ctx->src_sg_hold = new_sg;
} else {
ta_ctx->src_sg = req->src;
ta_ctx->src_sg_hold = NULL;
}
src_ptr = sg_virt(ta_ctx->src_sg);
if ((!req->nbytes) || (!ta_ctx->src_sg)) {
/* null data */
src_ptr = NULL;
}
ta_ctx->dcb_input_data.app_in_address = src_ptr;
ta_ctx->dcb_input_data.data_in_size = req->nbytes - tail_len;
ta_ctx->dcb_input_data.app_out_address = NULL;
ta_ctx->dcb_input_data.block_size = block_size;
ta_ctx->dcb_input_data.tail_block_size = 0;
ta_ctx->dcb_input_data.is_applet = 0;
ta_ctx->dcb_input_data.src_sg = ta_ctx->src_sg;
ta_ctx->dcb_input_data.dst_sg = NULL;
int_error = sep_create_dcb_dmatables_context_kernel(
ta_ctx->sep_used,
&ta_ctx->dcb_region,
&ta_ctx->dmatables_region,
&ta_ctx->dma_ctx,
&ta_ctx->dcb_input_data,
1);
if (int_error) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"hash update dma table create failed\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
return;
}
/* Construct message to SEP */
sep_make_header(ta_ctx, &msg_offset, SEP_HASH_SINGLE_OPCODE);
sep_write_msg(ta_ctx, &ta_ctx->hash_opmode,
sizeof(u32), sizeof(u32), &msg_offset, 0);
msg[0] = (u32)0;
msg[1] = (u32)0;
msg[2] = (u32)0;
sep_write_msg(ta_ctx, msg, sizeof(u32) * 3, sizeof(u32) * 3,
&msg_offset, 0);
/* Tail */
sep_write_msg(ta_ctx, &tail_len, sizeof(u32),
sizeof(u32), &msg_offset, 0);
if (tail_len) {
copy_result = sep_copy_offset_sg(
ta_ctx->sep_used,
ta_ctx->src_sg,
req->nbytes - tail_len,
small_buf, tail_len);
if (copy_result != tail_len) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"sg tail copy failure in hash block\n");
sep_crypto_release(sctx, ta_ctx, -ENOMEM);
return;
}
sep_write_msg(ta_ctx, small_buf, tail_len,
sizeof(u32) * 32, &msg_offset, 1);
} else {
msg_offset += sizeof(u32) * 32;
}
sep_end_msg(ta_ctx, msg_offset);
are_we_done_yet = 0;
result = sep_crypto_take_sep(ta_ctx);
if (result) {
dev_warn(&ta_ctx->sep_used->pdev->dev,
"sep_hash_digest take sep failed\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
}
/* now we sit and wait up to a fixed time for completion */
end_time = jiffies + (WAIT_TIME * HZ);
while ((time_before(jiffies, end_time)) && (are_we_done_yet == 0))
schedule();
/* Done waiting; still not done yet? */
if (are_we_done_yet == 0) {
dev_dbg(&ta_ctx->sep_used->pdev->dev,
"hash digest job never got done\n");
sep_crypto_release(sctx, ta_ctx, -EINVAL);
return;
}
}
/**
* This is what is called by each of the API's provided
* in the kernel crypto descriptors. It is run in a process
* context using the kernel workqueues. Therefore it can
* be put to sleep.
*/
static void sep_dequeuer(void *data)
{
struct crypto_queue *this_queue;
struct crypto_async_request *async_req;
struct crypto_async_request *backlog;
struct ablkcipher_request *cypher_req;
struct ahash_request *hash_req;
struct sep_system_ctx *sctx;
struct crypto_ahash *hash_tfm;
struct this_task_ctx *ta_ctx;
this_queue = (struct crypto_queue *)data;
spin_lock_irq(&queue_lock);
backlog = crypto_get_backlog(this_queue);
async_req = crypto_dequeue_request(this_queue);
spin_unlock_irq(&queue_lock);
if (!async_req) {
pr_debug("sep crypto queue is empty\n");
return;
}
if (backlog) {
pr_debug("sep crypto backlog set\n");
if (backlog->complete)
backlog->complete(backlog, -EINPROGRESS);
backlog = NULL;
}
if (!async_req->tfm) {
pr_debug("sep crypto queue null tfm\n");
return;
}
if (!async_req->tfm->__crt_alg) {
pr_debug("sep crypto queue null __crt_alg\n");
return;
}
if (!async_req->tfm->__crt_alg->cra_type) {
pr_debug("sep crypto queue null cra_type\n");
return;
}
/* we have stuff in the queue */
if (async_req->tfm->__crt_alg->cra_type !=
&crypto_ahash_type) {
/* This is for a cypher */
pr_debug("sep crypto queue doing cipher\n");
cypher_req = container_of(async_req,
struct ablkcipher_request,
base);
if (!cypher_req) {
pr_debug("sep crypto queue null cypher_req\n");
return;
}
sep_crypto_block((void *)cypher_req);
return;
} else {
/* This is a hash */
pr_debug("sep crypto queue doing hash\n");
/**
* This is a bit more complex than cipher; we
* need to figure out what type of operation
*/
hash_req = ahash_request_cast(async_req);
if (!hash_req) {
pr_debug("sep crypto queue null hash_req\n");
return;
}
hash_tfm = crypto_ahash_reqtfm(hash_req);
if (!hash_tfm) {
pr_debug("sep crypto queue null hash_tfm\n");
return;
}
sctx = crypto_ahash_ctx(hash_tfm);
if (!sctx) {
pr_debug("sep crypto queue null sctx\n");
return;
}
ta_ctx = ahash_request_ctx(hash_req);
if (ta_ctx->current_hash_stage == HASH_INIT) {
pr_debug("sep crypto queue hash init\n");
sep_hash_init((void *)hash_req);
return;
} else if (ta_ctx->current_hash_stage == HASH_UPDATE) {
pr_debug("sep crypto queue hash update\n");
sep_hash_update((void *)hash_req);
return;
} else if (ta_ctx->current_hash_stage == HASH_FINISH) {
pr_debug("sep crypto queue hash final\n");
sep_hash_final((void *)hash_req);
return;
} else if (ta_ctx->current_hash_stage == HASH_DIGEST) {
pr_debug("sep crypto queue hash digest\n");
sep_hash_digest((void *)hash_req);
return;
} else if (ta_ctx->current_hash_stage == HASH_FINUP_DATA) {
pr_debug("sep crypto queue hash digest\n");
sep_hash_update((void *)hash_req);
return;
} else if (ta_ctx->current_hash_stage == HASH_FINUP_FINISH) {
pr_debug("sep crypto queue hash digest\n");
sep_hash_final((void *)hash_req);
return;
} else {
pr_debug("sep crypto queue hash oops nothing\n");
return;
}
}
}
static int sep_sha1_init(struct ahash_request *req)
{
int error;
int error1;
struct this_task_ctx *ta_ctx = ahash_request_ctx(req);
pr_debug("sep - doing sha1 init\n");
/* Clear out task context */
memset(ta_ctx, 0, sizeof(struct this_task_ctx));
ta_ctx->sep_used = sep_dev;
ta_ctx->current_request = SHA1;
ta_ctx->current_hash_req = req;
ta_ctx->current_cypher_req = NULL;
ta_ctx->hash_opmode = SEP_HASH_SHA1;
ta_ctx->current_hash_stage = HASH_INIT;
/* lock necessary so that only one entity touches the queues */
spin_lock_irq(&queue_lock);
error = crypto_enqueue_request(&sep_queue, &req->base);
if ((error != 0) && (error != -EINPROGRESS))
pr_debug(" sep - crypto enqueue failed: %x\n",
error);
error1 = sep_submit_work(ta_ctx->sep_used->workqueue,
sep_dequeuer, (void *)&sep_queue);
if (error1)
pr_debug(" sep - workqueue submit failed: %x\n",
error1);
spin_unlock_irq(&queue_lock);
/* We return result of crypto enqueue */
return error;
}
static int sep_sha1_update(struct ahash_request *req)
{
int error;
int error1;
struct this_task_ctx *ta_ctx = ahash_request_ctx(req);
pr_debug("sep - doing sha1 update\n");
ta_ctx->sep_used = sep_dev;
ta_ctx->current_request = SHA1;
ta_ctx->current_hash_req = req;
ta_ctx->current_cypher_req = NULL;
ta_ctx->hash_opmode = SEP_HASH_SHA1;
ta_ctx->current_hash_stage = HASH_UPDATE;
/* lock necessary so that only one entity touches the queues */
spin_lock_irq(&queue_lock);
error = crypto_enqueue_request(&sep_queue, &req->base);
if ((error != 0) && (error != -EINPROGRESS))
pr_debug(" sep - crypto enqueue failed: %x\n",
error);
error1 = sep_submit_work(ta_ctx->sep_used->workqueue,
sep_dequeuer, (void *)&sep_queue);
if (error1)
pr_debug(" sep - workqueue submit failed: %x\n",
error1);
spin_unlock_irq(&queue_lock);
/* We return result of crypto enqueue */
return error;
}
static int sep_sha1_final(struct ahash_request *req)
{
int error;
int error1;
struct this_task_ctx *ta_ctx = ahash_request_ctx(req);
pr_debug("sep - doing sha1 final\n");
ta_ctx->sep_used = sep_dev;
ta_ctx->current_request = SHA1;
ta_ctx->current_hash_req = req;
ta_ctx->current_cypher_req = NULL;
ta_ctx->hash_opmode = SEP_HASH_SHA1;
ta_ctx->current_hash_stage = HASH_FINISH;
/* lock necessary so that only one entity touches the queues */
spin_lock_irq(&queue_lock);
error = crypto_enqueue_request(&sep_queue, &req->base);
if ((error != 0) && (error != -EINPROGRESS))
pr_debug(" sep - crypto enqueue failed: %x\n",
error);
error1 = sep_submit_work(ta_ctx->sep_used->workqueue,
sep_dequeuer, (void *)&sep_queue);
if (error1)
pr_debug(" sep - workqueue submit failed: %x\n",
error1);
spin_unlock_irq(&queue_lock);
/* We return result of crypto enqueue */
return error;
}
static int sep_sha1_digest(struct ahash_request *req)
{
int error;
int error1;
struct this_task_ctx *ta_ctx = ahash_request_ctx(req);
pr_debug("sep - doing sha1 digest\n");
/* Clear out task context */
memset(ta_ctx, 0, sizeof(struct this_task_ctx));
ta_ctx->sep_used = sep_dev;
ta_ctx->current_request = SHA1;
ta_ctx->current_hash_req = req;
ta_ctx->current_cypher_req = NULL;
ta_ctx->hash_opmode = SEP_HASH_SHA1;
ta_ctx->current_hash_stage = HASH_DIGEST;