mirror of
https://github.com/torvalds/linux.git
synced 2024-11-16 00:52:01 +00:00
7cc2835083
The error handling in mv_probe() was a bit messed up. There were some gotos to the wrong labels so it ended up releasing stuff that that hadn't been aquired and not releasing stuff that was meant to be released. I shuffled it around a bit to fix it and make it clearer. Signed-off-by: Dan Carpenter <error27@gmail.com> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
1154 lines
28 KiB
C
1154 lines
28 KiB
C
/*
|
|
* Support for Marvell's crypto engine which can be found on some Orion5X
|
|
* boards.
|
|
*
|
|
* Author: Sebastian Andrzej Siewior < sebastian at breakpoint dot cc >
|
|
* License: GPLv2
|
|
*
|
|
*/
|
|
#include <crypto/aes.h>
|
|
#include <crypto/algapi.h>
|
|
#include <linux/crypto.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/scatterlist.h>
|
|
#include <linux/slab.h>
|
|
#include <crypto/internal/hash.h>
|
|
#include <crypto/sha.h>
|
|
|
|
#include "mv_cesa.h"
|
|
|
|
#define MV_CESA "MV-CESA:"
|
|
#define MAX_HW_HASH_SIZE 0xFFFF
|
|
|
|
/*
|
|
* STM:
|
|
* /---------------------------------------\
|
|
* | | request complete
|
|
* \./ |
|
|
* IDLE -> new request -> BUSY -> done -> DEQUEUE
|
|
* /°\ |
|
|
* | | more scatter entries
|
|
* \________________/
|
|
*/
|
|
enum engine_status {
|
|
ENGINE_IDLE,
|
|
ENGINE_BUSY,
|
|
ENGINE_W_DEQUEUE,
|
|
};
|
|
|
|
/**
|
|
* struct req_progress - used for every crypt request
|
|
* @src_sg_it: sg iterator for src
|
|
* @dst_sg_it: sg iterator for dst
|
|
* @sg_src_left: bytes left in src to process (scatter list)
|
|
* @src_start: offset to add to src start position (scatter list)
|
|
* @crypt_len: length of current hw crypt/hash process
|
|
* @hw_nbytes: total bytes to process in hw for this request
|
|
* @copy_back: whether to copy data back (crypt) or not (hash)
|
|
* @sg_dst_left: bytes left dst to process in this scatter list
|
|
* @dst_start: offset to add to dst start position (scatter list)
|
|
* @hw_processed_bytes: number of bytes processed by hw (request).
|
|
*
|
|
* sg helper are used to iterate over the scatterlist. Since the size of the
|
|
* SRAM may be less than the scatter size, this struct struct is used to keep
|
|
* track of progress within current scatterlist.
|
|
*/
|
|
struct req_progress {
|
|
struct sg_mapping_iter src_sg_it;
|
|
struct sg_mapping_iter dst_sg_it;
|
|
void (*complete) (void);
|
|
void (*process) (int is_first);
|
|
|
|
/* src mostly */
|
|
int sg_src_left;
|
|
int src_start;
|
|
int crypt_len;
|
|
int hw_nbytes;
|
|
/* dst mostly */
|
|
int copy_back;
|
|
int sg_dst_left;
|
|
int dst_start;
|
|
int hw_processed_bytes;
|
|
};
|
|
|
|
struct crypto_priv {
|
|
void __iomem *reg;
|
|
void __iomem *sram;
|
|
int irq;
|
|
struct task_struct *queue_th;
|
|
|
|
/* the lock protects queue and eng_st */
|
|
spinlock_t lock;
|
|
struct crypto_queue queue;
|
|
enum engine_status eng_st;
|
|
struct crypto_async_request *cur_req;
|
|
struct req_progress p;
|
|
int max_req_size;
|
|
int sram_size;
|
|
int has_sha1;
|
|
int has_hmac_sha1;
|
|
};
|
|
|
|
static struct crypto_priv *cpg;
|
|
|
|
struct mv_ctx {
|
|
u8 aes_enc_key[AES_KEY_LEN];
|
|
u32 aes_dec_key[8];
|
|
int key_len;
|
|
u32 need_calc_aes_dkey;
|
|
};
|
|
|
|
enum crypto_op {
|
|
COP_AES_ECB,
|
|
COP_AES_CBC,
|
|
};
|
|
|
|
struct mv_req_ctx {
|
|
enum crypto_op op;
|
|
int decrypt;
|
|
};
|
|
|
|
enum hash_op {
|
|
COP_SHA1,
|
|
COP_HMAC_SHA1
|
|
};
|
|
|
|
struct mv_tfm_hash_ctx {
|
|
struct crypto_shash *fallback;
|
|
struct crypto_shash *base_hash;
|
|
u32 ivs[2 * SHA1_DIGEST_SIZE / 4];
|
|
int count_add;
|
|
enum hash_op op;
|
|
};
|
|
|
|
struct mv_req_hash_ctx {
|
|
u64 count;
|
|
u32 state[SHA1_DIGEST_SIZE / 4];
|
|
u8 buffer[SHA1_BLOCK_SIZE];
|
|
int first_hash; /* marks that we don't have previous state */
|
|
int last_chunk; /* marks that this is the 'final' request */
|
|
int extra_bytes; /* unprocessed bytes in buffer */
|
|
enum hash_op op;
|
|
int count_add;
|
|
struct scatterlist dummysg;
|
|
};
|
|
|
|
static void compute_aes_dec_key(struct mv_ctx *ctx)
|
|
{
|
|
struct crypto_aes_ctx gen_aes_key;
|
|
int key_pos;
|
|
|
|
if (!ctx->need_calc_aes_dkey)
|
|
return;
|
|
|
|
crypto_aes_expand_key(&gen_aes_key, ctx->aes_enc_key, ctx->key_len);
|
|
|
|
key_pos = ctx->key_len + 24;
|
|
memcpy(ctx->aes_dec_key, &gen_aes_key.key_enc[key_pos], 4 * 4);
|
|
switch (ctx->key_len) {
|
|
case AES_KEYSIZE_256:
|
|
key_pos -= 2;
|
|
/* fall */
|
|
case AES_KEYSIZE_192:
|
|
key_pos -= 2;
|
|
memcpy(&ctx->aes_dec_key[4], &gen_aes_key.key_enc[key_pos],
|
|
4 * 4);
|
|
break;
|
|
}
|
|
ctx->need_calc_aes_dkey = 0;
|
|
}
|
|
|
|
static int mv_setkey_aes(struct crypto_ablkcipher *cipher, const u8 *key,
|
|
unsigned int len)
|
|
{
|
|
struct crypto_tfm *tfm = crypto_ablkcipher_tfm(cipher);
|
|
struct mv_ctx *ctx = crypto_tfm_ctx(tfm);
|
|
|
|
switch (len) {
|
|
case AES_KEYSIZE_128:
|
|
case AES_KEYSIZE_192:
|
|
case AES_KEYSIZE_256:
|
|
break;
|
|
default:
|
|
crypto_ablkcipher_set_flags(cipher, CRYPTO_TFM_RES_BAD_KEY_LEN);
|
|
return -EINVAL;
|
|
}
|
|
ctx->key_len = len;
|
|
ctx->need_calc_aes_dkey = 1;
|
|
|
|
memcpy(ctx->aes_enc_key, key, AES_KEY_LEN);
|
|
return 0;
|
|
}
|
|
|
|
static void copy_src_to_buf(struct req_progress *p, char *dbuf, int len)
|
|
{
|
|
int ret;
|
|
void *sbuf;
|
|
int copied = 0;
|
|
|
|
while (1) {
|
|
if (!p->sg_src_left) {
|
|
ret = sg_miter_next(&p->src_sg_it);
|
|
BUG_ON(!ret);
|
|
p->sg_src_left = p->src_sg_it.length;
|
|
p->src_start = 0;
|
|
}
|
|
|
|
sbuf = p->src_sg_it.addr + p->src_start;
|
|
|
|
if (p->sg_src_left <= len - copied) {
|
|
memcpy(dbuf + copied, sbuf, p->sg_src_left);
|
|
copied += p->sg_src_left;
|
|
p->sg_src_left = 0;
|
|
if (copied >= len)
|
|
break;
|
|
} else {
|
|
int copy_len = len - copied;
|
|
memcpy(dbuf + copied, sbuf, copy_len);
|
|
p->src_start += copy_len;
|
|
p->sg_src_left -= copy_len;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void setup_data_in(void)
|
|
{
|
|
struct req_progress *p = &cpg->p;
|
|
int data_in_sram =
|
|
min(p->hw_nbytes - p->hw_processed_bytes, cpg->max_req_size);
|
|
copy_src_to_buf(p, cpg->sram + SRAM_DATA_IN_START + p->crypt_len,
|
|
data_in_sram - p->crypt_len);
|
|
p->crypt_len = data_in_sram;
|
|
}
|
|
|
|
static void mv_process_current_q(int first_block)
|
|
{
|
|
struct ablkcipher_request *req = ablkcipher_request_cast(cpg->cur_req);
|
|
struct mv_ctx *ctx = crypto_tfm_ctx(req->base.tfm);
|
|
struct mv_req_ctx *req_ctx = ablkcipher_request_ctx(req);
|
|
struct sec_accel_config op;
|
|
|
|
switch (req_ctx->op) {
|
|
case COP_AES_ECB:
|
|
op.config = CFG_OP_CRYPT_ONLY | CFG_ENCM_AES | CFG_ENC_MODE_ECB;
|
|
break;
|
|
case COP_AES_CBC:
|
|
default:
|
|
op.config = CFG_OP_CRYPT_ONLY | CFG_ENCM_AES | CFG_ENC_MODE_CBC;
|
|
op.enc_iv = ENC_IV_POINT(SRAM_DATA_IV) |
|
|
ENC_IV_BUF_POINT(SRAM_DATA_IV_BUF);
|
|
if (first_block)
|
|
memcpy(cpg->sram + SRAM_DATA_IV, req->info, 16);
|
|
break;
|
|
}
|
|
if (req_ctx->decrypt) {
|
|
op.config |= CFG_DIR_DEC;
|
|
memcpy(cpg->sram + SRAM_DATA_KEY_P, ctx->aes_dec_key,
|
|
AES_KEY_LEN);
|
|
} else {
|
|
op.config |= CFG_DIR_ENC;
|
|
memcpy(cpg->sram + SRAM_DATA_KEY_P, ctx->aes_enc_key,
|
|
AES_KEY_LEN);
|
|
}
|
|
|
|
switch (ctx->key_len) {
|
|
case AES_KEYSIZE_128:
|
|
op.config |= CFG_AES_LEN_128;
|
|
break;
|
|
case AES_KEYSIZE_192:
|
|
op.config |= CFG_AES_LEN_192;
|
|
break;
|
|
case AES_KEYSIZE_256:
|
|
op.config |= CFG_AES_LEN_256;
|
|
break;
|
|
}
|
|
op.enc_p = ENC_P_SRC(SRAM_DATA_IN_START) |
|
|
ENC_P_DST(SRAM_DATA_OUT_START);
|
|
op.enc_key_p = SRAM_DATA_KEY_P;
|
|
|
|
setup_data_in();
|
|
op.enc_len = cpg->p.crypt_len;
|
|
memcpy(cpg->sram + SRAM_CONFIG, &op,
|
|
sizeof(struct sec_accel_config));
|
|
|
|
writel(SRAM_CONFIG, cpg->reg + SEC_ACCEL_DESC_P0);
|
|
/* GO */
|
|
writel(SEC_CMD_EN_SEC_ACCL0, cpg->reg + SEC_ACCEL_CMD);
|
|
|
|
/*
|
|
* XXX: add timer if the interrupt does not occur for some mystery
|
|
* reason
|
|
*/
|
|
}
|
|
|
|
static void mv_crypto_algo_completion(void)
|
|
{
|
|
struct ablkcipher_request *req = ablkcipher_request_cast(cpg->cur_req);
|
|
struct mv_req_ctx *req_ctx = ablkcipher_request_ctx(req);
|
|
|
|
sg_miter_stop(&cpg->p.src_sg_it);
|
|
sg_miter_stop(&cpg->p.dst_sg_it);
|
|
|
|
if (req_ctx->op != COP_AES_CBC)
|
|
return ;
|
|
|
|
memcpy(req->info, cpg->sram + SRAM_DATA_IV_BUF, 16);
|
|
}
|
|
|
|
static void mv_process_hash_current(int first_block)
|
|
{
|
|
struct ahash_request *req = ahash_request_cast(cpg->cur_req);
|
|
struct mv_req_hash_ctx *req_ctx = ahash_request_ctx(req);
|
|
struct req_progress *p = &cpg->p;
|
|
struct sec_accel_config op = { 0 };
|
|
int is_last;
|
|
|
|
switch (req_ctx->op) {
|
|
case COP_SHA1:
|
|
default:
|
|
op.config = CFG_OP_MAC_ONLY | CFG_MACM_SHA1;
|
|
break;
|
|
case COP_HMAC_SHA1:
|
|
op.config = CFG_OP_MAC_ONLY | CFG_MACM_HMAC_SHA1;
|
|
break;
|
|
}
|
|
|
|
op.mac_src_p =
|
|
MAC_SRC_DATA_P(SRAM_DATA_IN_START) | MAC_SRC_TOTAL_LEN((u32)
|
|
req_ctx->
|
|
count);
|
|
|
|
setup_data_in();
|
|
|
|
op.mac_digest =
|
|
MAC_DIGEST_P(SRAM_DIGEST_BUF) | MAC_FRAG_LEN(p->crypt_len);
|
|
op.mac_iv =
|
|
MAC_INNER_IV_P(SRAM_HMAC_IV_IN) |
|
|
MAC_OUTER_IV_P(SRAM_HMAC_IV_OUT);
|
|
|
|
is_last = req_ctx->last_chunk
|
|
&& (p->hw_processed_bytes + p->crypt_len >= p->hw_nbytes)
|
|
&& (req_ctx->count <= MAX_HW_HASH_SIZE);
|
|
if (req_ctx->first_hash) {
|
|
if (is_last)
|
|
op.config |= CFG_NOT_FRAG;
|
|
else
|
|
op.config |= CFG_FIRST_FRAG;
|
|
|
|
req_ctx->first_hash = 0;
|
|
} else {
|
|
if (is_last)
|
|
op.config |= CFG_LAST_FRAG;
|
|
else
|
|
op.config |= CFG_MID_FRAG;
|
|
}
|
|
|
|
memcpy(cpg->sram + SRAM_CONFIG, &op, sizeof(struct sec_accel_config));
|
|
|
|
writel(SRAM_CONFIG, cpg->reg + SEC_ACCEL_DESC_P0);
|
|
/* GO */
|
|
writel(SEC_CMD_EN_SEC_ACCL0, cpg->reg + SEC_ACCEL_CMD);
|
|
|
|
/*
|
|
* XXX: add timer if the interrupt does not occur for some mystery
|
|
* reason
|
|
*/
|
|
}
|
|
|
|
static inline int mv_hash_import_sha1_ctx(const struct mv_req_hash_ctx *ctx,
|
|
struct shash_desc *desc)
|
|
{
|
|
int i;
|
|
struct sha1_state shash_state;
|
|
|
|
shash_state.count = ctx->count + ctx->count_add;
|
|
for (i = 0; i < 5; i++)
|
|
shash_state.state[i] = ctx->state[i];
|
|
memcpy(shash_state.buffer, ctx->buffer, sizeof(shash_state.buffer));
|
|
return crypto_shash_import(desc, &shash_state);
|
|
}
|
|
|
|
static int mv_hash_final_fallback(struct ahash_request *req)
|
|
{
|
|
const struct mv_tfm_hash_ctx *tfm_ctx = crypto_tfm_ctx(req->base.tfm);
|
|
struct mv_req_hash_ctx *req_ctx = ahash_request_ctx(req);
|
|
struct {
|
|
struct shash_desc shash;
|
|
char ctx[crypto_shash_descsize(tfm_ctx->fallback)];
|
|
} desc;
|
|
int rc;
|
|
|
|
desc.shash.tfm = tfm_ctx->fallback;
|
|
desc.shash.flags = CRYPTO_TFM_REQ_MAY_SLEEP;
|
|
if (unlikely(req_ctx->first_hash)) {
|
|
crypto_shash_init(&desc.shash);
|
|
crypto_shash_update(&desc.shash, req_ctx->buffer,
|
|
req_ctx->extra_bytes);
|
|
} else {
|
|
/* only SHA1 for now....
|
|
*/
|
|
rc = mv_hash_import_sha1_ctx(req_ctx, &desc.shash);
|
|
if (rc)
|
|
goto out;
|
|
}
|
|
rc = crypto_shash_final(&desc.shash, req->result);
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static void mv_hash_algo_completion(void)
|
|
{
|
|
struct ahash_request *req = ahash_request_cast(cpg->cur_req);
|
|
struct mv_req_hash_ctx *ctx = ahash_request_ctx(req);
|
|
|
|
if (ctx->extra_bytes)
|
|
copy_src_to_buf(&cpg->p, ctx->buffer, ctx->extra_bytes);
|
|
sg_miter_stop(&cpg->p.src_sg_it);
|
|
|
|
ctx->state[0] = readl(cpg->reg + DIGEST_INITIAL_VAL_A);
|
|
ctx->state[1] = readl(cpg->reg + DIGEST_INITIAL_VAL_B);
|
|
ctx->state[2] = readl(cpg->reg + DIGEST_INITIAL_VAL_C);
|
|
ctx->state[3] = readl(cpg->reg + DIGEST_INITIAL_VAL_D);
|
|
ctx->state[4] = readl(cpg->reg + DIGEST_INITIAL_VAL_E);
|
|
|
|
if (likely(ctx->last_chunk)) {
|
|
if (likely(ctx->count <= MAX_HW_HASH_SIZE)) {
|
|
memcpy(req->result, cpg->sram + SRAM_DIGEST_BUF,
|
|
crypto_ahash_digestsize(crypto_ahash_reqtfm
|
|
(req)));
|
|
} else
|
|
mv_hash_final_fallback(req);
|
|
}
|
|
}
|
|
|
|
static void dequeue_complete_req(void)
|
|
{
|
|
struct crypto_async_request *req = cpg->cur_req;
|
|
void *buf;
|
|
int ret;
|
|
cpg->p.hw_processed_bytes += cpg->p.crypt_len;
|
|
if (cpg->p.copy_back) {
|
|
int need_copy_len = cpg->p.crypt_len;
|
|
int sram_offset = 0;
|
|
do {
|
|
int dst_copy;
|
|
|
|
if (!cpg->p.sg_dst_left) {
|
|
ret = sg_miter_next(&cpg->p.dst_sg_it);
|
|
BUG_ON(!ret);
|
|
cpg->p.sg_dst_left = cpg->p.dst_sg_it.length;
|
|
cpg->p.dst_start = 0;
|
|
}
|
|
|
|
buf = cpg->p.dst_sg_it.addr;
|
|
buf += cpg->p.dst_start;
|
|
|
|
dst_copy = min(need_copy_len, cpg->p.sg_dst_left);
|
|
|
|
memcpy(buf,
|
|
cpg->sram + SRAM_DATA_OUT_START + sram_offset,
|
|
dst_copy);
|
|
sram_offset += dst_copy;
|
|
cpg->p.sg_dst_left -= dst_copy;
|
|
need_copy_len -= dst_copy;
|
|
cpg->p.dst_start += dst_copy;
|
|
} while (need_copy_len > 0);
|
|
}
|
|
|
|
cpg->p.crypt_len = 0;
|
|
|
|
BUG_ON(cpg->eng_st != ENGINE_W_DEQUEUE);
|
|
if (cpg->p.hw_processed_bytes < cpg->p.hw_nbytes) {
|
|
/* process next scatter list entry */
|
|
cpg->eng_st = ENGINE_BUSY;
|
|
cpg->p.process(0);
|
|
} else {
|
|
cpg->p.complete();
|
|
cpg->eng_st = ENGINE_IDLE;
|
|
local_bh_disable();
|
|
req->complete(req, 0);
|
|
local_bh_enable();
|
|
}
|
|
}
|
|
|
|
static int count_sgs(struct scatterlist *sl, unsigned int total_bytes)
|
|
{
|
|
int i = 0;
|
|
size_t cur_len;
|
|
|
|
while (1) {
|
|
cur_len = sl[i].length;
|
|
++i;
|
|
if (total_bytes > cur_len)
|
|
total_bytes -= cur_len;
|
|
else
|
|
break;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
static void mv_start_new_crypt_req(struct ablkcipher_request *req)
|
|
{
|
|
struct req_progress *p = &cpg->p;
|
|
int num_sgs;
|
|
|
|
cpg->cur_req = &req->base;
|
|
memset(p, 0, sizeof(struct req_progress));
|
|
p->hw_nbytes = req->nbytes;
|
|
p->complete = mv_crypto_algo_completion;
|
|
p->process = mv_process_current_q;
|
|
p->copy_back = 1;
|
|
|
|
num_sgs = count_sgs(req->src, req->nbytes);
|
|
sg_miter_start(&p->src_sg_it, req->src, num_sgs, SG_MITER_FROM_SG);
|
|
|
|
num_sgs = count_sgs(req->dst, req->nbytes);
|
|
sg_miter_start(&p->dst_sg_it, req->dst, num_sgs, SG_MITER_TO_SG);
|
|
|
|
mv_process_current_q(1);
|
|
}
|
|
|
|
static void mv_start_new_hash_req(struct ahash_request *req)
|
|
{
|
|
struct req_progress *p = &cpg->p;
|
|
struct mv_req_hash_ctx *ctx = ahash_request_ctx(req);
|
|
const struct mv_tfm_hash_ctx *tfm_ctx = crypto_tfm_ctx(req->base.tfm);
|
|
int num_sgs, hw_bytes, old_extra_bytes, rc;
|
|
cpg->cur_req = &req->base;
|
|
memset(p, 0, sizeof(struct req_progress));
|
|
hw_bytes = req->nbytes + ctx->extra_bytes;
|
|
old_extra_bytes = ctx->extra_bytes;
|
|
|
|
if (unlikely(ctx->extra_bytes)) {
|
|
memcpy(cpg->sram + SRAM_DATA_IN_START, ctx->buffer,
|
|
ctx->extra_bytes);
|
|
p->crypt_len = ctx->extra_bytes;
|
|
}
|
|
|
|
memcpy(cpg->sram + SRAM_HMAC_IV_IN, tfm_ctx->ivs, sizeof(tfm_ctx->ivs));
|
|
|
|
if (unlikely(!ctx->first_hash)) {
|
|
writel(ctx->state[0], cpg->reg + DIGEST_INITIAL_VAL_A);
|
|
writel(ctx->state[1], cpg->reg + DIGEST_INITIAL_VAL_B);
|
|
writel(ctx->state[2], cpg->reg + DIGEST_INITIAL_VAL_C);
|
|
writel(ctx->state[3], cpg->reg + DIGEST_INITIAL_VAL_D);
|
|
writel(ctx->state[4], cpg->reg + DIGEST_INITIAL_VAL_E);
|
|
}
|
|
|
|
ctx->extra_bytes = hw_bytes % SHA1_BLOCK_SIZE;
|
|
if (ctx->extra_bytes != 0
|
|
&& (!ctx->last_chunk || ctx->count > MAX_HW_HASH_SIZE))
|
|
hw_bytes -= ctx->extra_bytes;
|
|
else
|
|
ctx->extra_bytes = 0;
|
|
|
|
num_sgs = count_sgs(req->src, req->nbytes);
|
|
sg_miter_start(&p->src_sg_it, req->src, num_sgs, SG_MITER_FROM_SG);
|
|
|
|
if (hw_bytes) {
|
|
p->hw_nbytes = hw_bytes;
|
|
p->complete = mv_hash_algo_completion;
|
|
p->process = mv_process_hash_current;
|
|
|
|
mv_process_hash_current(1);
|
|
} else {
|
|
copy_src_to_buf(p, ctx->buffer + old_extra_bytes,
|
|
ctx->extra_bytes - old_extra_bytes);
|
|
sg_miter_stop(&p->src_sg_it);
|
|
if (ctx->last_chunk)
|
|
rc = mv_hash_final_fallback(req);
|
|
else
|
|
rc = 0;
|
|
cpg->eng_st = ENGINE_IDLE;
|
|
local_bh_disable();
|
|
req->base.complete(&req->base, rc);
|
|
local_bh_enable();
|
|
}
|
|
}
|
|
|
|
static int queue_manag(void *data)
|
|
{
|
|
cpg->eng_st = ENGINE_IDLE;
|
|
do {
|
|
struct crypto_async_request *async_req = NULL;
|
|
struct crypto_async_request *backlog;
|
|
|
|
__set_current_state(TASK_INTERRUPTIBLE);
|
|
|
|
if (cpg->eng_st == ENGINE_W_DEQUEUE)
|
|
dequeue_complete_req();
|
|
|
|
spin_lock_irq(&cpg->lock);
|
|
if (cpg->eng_st == ENGINE_IDLE) {
|
|
backlog = crypto_get_backlog(&cpg->queue);
|
|
async_req = crypto_dequeue_request(&cpg->queue);
|
|
if (async_req) {
|
|
BUG_ON(cpg->eng_st != ENGINE_IDLE);
|
|
cpg->eng_st = ENGINE_BUSY;
|
|
}
|
|
}
|
|
spin_unlock_irq(&cpg->lock);
|
|
|
|
if (backlog) {
|
|
backlog->complete(backlog, -EINPROGRESS);
|
|
backlog = NULL;
|
|
}
|
|
|
|
if (async_req) {
|
|
if (async_req->tfm->__crt_alg->cra_type !=
|
|
&crypto_ahash_type) {
|
|
struct ablkcipher_request *req =
|
|
container_of(async_req,
|
|
struct ablkcipher_request,
|
|
base);
|
|
mv_start_new_crypt_req(req);
|
|
} else {
|
|
struct ahash_request *req =
|
|
ahash_request_cast(async_req);
|
|
mv_start_new_hash_req(req);
|
|
}
|
|
async_req = NULL;
|
|
}
|
|
|
|
schedule();
|
|
|
|
} while (!kthread_should_stop());
|
|
return 0;
|
|
}
|
|
|
|
static int mv_handle_req(struct crypto_async_request *req)
|
|
{
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
spin_lock_irqsave(&cpg->lock, flags);
|
|
ret = crypto_enqueue_request(&cpg->queue, req);
|
|
spin_unlock_irqrestore(&cpg->lock, flags);
|
|
wake_up_process(cpg->queue_th);
|
|
return ret;
|
|
}
|
|
|
|
static int mv_enc_aes_ecb(struct ablkcipher_request *req)
|
|
{
|
|
struct mv_req_ctx *req_ctx = ablkcipher_request_ctx(req);
|
|
|
|
req_ctx->op = COP_AES_ECB;
|
|
req_ctx->decrypt = 0;
|
|
|
|
return mv_handle_req(&req->base);
|
|
}
|
|
|
|
static int mv_dec_aes_ecb(struct ablkcipher_request *req)
|
|
{
|
|
struct mv_ctx *ctx = crypto_tfm_ctx(req->base.tfm);
|
|
struct mv_req_ctx *req_ctx = ablkcipher_request_ctx(req);
|
|
|
|
req_ctx->op = COP_AES_ECB;
|
|
req_ctx->decrypt = 1;
|
|
|
|
compute_aes_dec_key(ctx);
|
|
return mv_handle_req(&req->base);
|
|
}
|
|
|
|
static int mv_enc_aes_cbc(struct ablkcipher_request *req)
|
|
{
|
|
struct mv_req_ctx *req_ctx = ablkcipher_request_ctx(req);
|
|
|
|
req_ctx->op = COP_AES_CBC;
|
|
req_ctx->decrypt = 0;
|
|
|
|
return mv_handle_req(&req->base);
|
|
}
|
|
|
|
static int mv_dec_aes_cbc(struct ablkcipher_request *req)
|
|
{
|
|
struct mv_ctx *ctx = crypto_tfm_ctx(req->base.tfm);
|
|
struct mv_req_ctx *req_ctx = ablkcipher_request_ctx(req);
|
|
|
|
req_ctx->op = COP_AES_CBC;
|
|
req_ctx->decrypt = 1;
|
|
|
|
compute_aes_dec_key(ctx);
|
|
return mv_handle_req(&req->base);
|
|
}
|
|
|
|
static int mv_cra_init(struct crypto_tfm *tfm)
|
|
{
|
|
tfm->crt_ablkcipher.reqsize = sizeof(struct mv_req_ctx);
|
|
return 0;
|
|
}
|
|
|
|
static void mv_init_hash_req_ctx(struct mv_req_hash_ctx *ctx, int op,
|
|
int is_last, unsigned int req_len,
|
|
int count_add)
|
|
{
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
ctx->op = op;
|
|
ctx->count = req_len;
|
|
ctx->first_hash = 1;
|
|
ctx->last_chunk = is_last;
|
|
ctx->count_add = count_add;
|
|
}
|
|
|
|
static void mv_update_hash_req_ctx(struct mv_req_hash_ctx *ctx, int is_last,
|
|
unsigned req_len)
|
|
{
|
|
ctx->last_chunk = is_last;
|
|
ctx->count += req_len;
|
|
}
|
|
|
|
static int mv_hash_init(struct ahash_request *req)
|
|
{
|
|
const struct mv_tfm_hash_ctx *tfm_ctx = crypto_tfm_ctx(req->base.tfm);
|
|
mv_init_hash_req_ctx(ahash_request_ctx(req), tfm_ctx->op, 0, 0,
|
|
tfm_ctx->count_add);
|
|
return 0;
|
|
}
|
|
|
|
static int mv_hash_update(struct ahash_request *req)
|
|
{
|
|
if (!req->nbytes)
|
|
return 0;
|
|
|
|
mv_update_hash_req_ctx(ahash_request_ctx(req), 0, req->nbytes);
|
|
return mv_handle_req(&req->base);
|
|
}
|
|
|
|
static int mv_hash_final(struct ahash_request *req)
|
|
{
|
|
struct mv_req_hash_ctx *ctx = ahash_request_ctx(req);
|
|
/* dummy buffer of 4 bytes */
|
|
sg_init_one(&ctx->dummysg, ctx->buffer, 4);
|
|
/* I think I'm allowed to do that... */
|
|
ahash_request_set_crypt(req, &ctx->dummysg, req->result, 0);
|
|
mv_update_hash_req_ctx(ctx, 1, 0);
|
|
return mv_handle_req(&req->base);
|
|
}
|
|
|
|
static int mv_hash_finup(struct ahash_request *req)
|
|
{
|
|
if (!req->nbytes)
|
|
return mv_hash_final(req);
|
|
|
|
mv_update_hash_req_ctx(ahash_request_ctx(req), 1, req->nbytes);
|
|
return mv_handle_req(&req->base);
|
|
}
|
|
|
|
static int mv_hash_digest(struct ahash_request *req)
|
|
{
|
|
const struct mv_tfm_hash_ctx *tfm_ctx = crypto_tfm_ctx(req->base.tfm);
|
|
mv_init_hash_req_ctx(ahash_request_ctx(req), tfm_ctx->op, 1,
|
|
req->nbytes, tfm_ctx->count_add);
|
|
return mv_handle_req(&req->base);
|
|
}
|
|
|
|
static void mv_hash_init_ivs(struct mv_tfm_hash_ctx *ctx, const void *istate,
|
|
const void *ostate)
|
|
{
|
|
const struct sha1_state *isha1_state = istate, *osha1_state = ostate;
|
|
int i;
|
|
for (i = 0; i < 5; i++) {
|
|
ctx->ivs[i] = cpu_to_be32(isha1_state->state[i]);
|
|
ctx->ivs[i + 5] = cpu_to_be32(osha1_state->state[i]);
|
|
}
|
|
}
|
|
|
|
static int mv_hash_setkey(struct crypto_ahash *tfm, const u8 * key,
|
|
unsigned int keylen)
|
|
{
|
|
int rc;
|
|
struct mv_tfm_hash_ctx *ctx = crypto_tfm_ctx(&tfm->base);
|
|
int bs, ds, ss;
|
|
|
|
if (!ctx->base_hash)
|
|
return 0;
|
|
|
|
rc = crypto_shash_setkey(ctx->fallback, key, keylen);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Can't see a way to extract the ipad/opad from the fallback tfm
|
|
so I'm basically copying code from the hmac module */
|
|
bs = crypto_shash_blocksize(ctx->base_hash);
|
|
ds = crypto_shash_digestsize(ctx->base_hash);
|
|
ss = crypto_shash_statesize(ctx->base_hash);
|
|
|
|
{
|
|
struct {
|
|
struct shash_desc shash;
|
|
char ctx[crypto_shash_descsize(ctx->base_hash)];
|
|
} desc;
|
|
unsigned int i;
|
|
char ipad[ss];
|
|
char opad[ss];
|
|
|
|
desc.shash.tfm = ctx->base_hash;
|
|
desc.shash.flags = crypto_shash_get_flags(ctx->base_hash) &
|
|
CRYPTO_TFM_REQ_MAY_SLEEP;
|
|
|
|
if (keylen > bs) {
|
|
int err;
|
|
|
|
err =
|
|
crypto_shash_digest(&desc.shash, key, keylen, ipad);
|
|
if (err)
|
|
return err;
|
|
|
|
keylen = ds;
|
|
} else
|
|
memcpy(ipad, key, keylen);
|
|
|
|
memset(ipad + keylen, 0, bs - keylen);
|
|
memcpy(opad, ipad, bs);
|
|
|
|
for (i = 0; i < bs; i++) {
|
|
ipad[i] ^= 0x36;
|
|
opad[i] ^= 0x5c;
|
|
}
|
|
|
|
rc = crypto_shash_init(&desc.shash) ? :
|
|
crypto_shash_update(&desc.shash, ipad, bs) ? :
|
|
crypto_shash_export(&desc.shash, ipad) ? :
|
|
crypto_shash_init(&desc.shash) ? :
|
|
crypto_shash_update(&desc.shash, opad, bs) ? :
|
|
crypto_shash_export(&desc.shash, opad);
|
|
|
|
if (rc == 0)
|
|
mv_hash_init_ivs(ctx, ipad, opad);
|
|
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
static int mv_cra_hash_init(struct crypto_tfm *tfm, const char *base_hash_name,
|
|
enum hash_op op, int count_add)
|
|
{
|
|
const char *fallback_driver_name = tfm->__crt_alg->cra_name;
|
|
struct mv_tfm_hash_ctx *ctx = crypto_tfm_ctx(tfm);
|
|
struct crypto_shash *fallback_tfm = NULL;
|
|
struct crypto_shash *base_hash = NULL;
|
|
int err = -ENOMEM;
|
|
|
|
ctx->op = op;
|
|
ctx->count_add = count_add;
|
|
|
|
/* Allocate a fallback and abort if it failed. */
|
|
fallback_tfm = crypto_alloc_shash(fallback_driver_name, 0,
|
|
CRYPTO_ALG_NEED_FALLBACK);
|
|
if (IS_ERR(fallback_tfm)) {
|
|
printk(KERN_WARNING MV_CESA
|
|
"Fallback driver '%s' could not be loaded!\n",
|
|
fallback_driver_name);
|
|
err = PTR_ERR(fallback_tfm);
|
|
goto out;
|
|
}
|
|
ctx->fallback = fallback_tfm;
|
|
|
|
if (base_hash_name) {
|
|
/* Allocate a hash to compute the ipad/opad of hmac. */
|
|
base_hash = crypto_alloc_shash(base_hash_name, 0,
|
|
CRYPTO_ALG_NEED_FALLBACK);
|
|
if (IS_ERR(base_hash)) {
|
|
printk(KERN_WARNING MV_CESA
|
|
"Base driver '%s' could not be loaded!\n",
|
|
base_hash_name);
|
|
err = PTR_ERR(fallback_tfm);
|
|
goto err_bad_base;
|
|
}
|
|
}
|
|
ctx->base_hash = base_hash;
|
|
|
|
crypto_ahash_set_reqsize(__crypto_ahash_cast(tfm),
|
|
sizeof(struct mv_req_hash_ctx) +
|
|
crypto_shash_descsize(ctx->fallback));
|
|
return 0;
|
|
err_bad_base:
|
|
crypto_free_shash(fallback_tfm);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static void mv_cra_hash_exit(struct crypto_tfm *tfm)
|
|
{
|
|
struct mv_tfm_hash_ctx *ctx = crypto_tfm_ctx(tfm);
|
|
|
|
crypto_free_shash(ctx->fallback);
|
|
if (ctx->base_hash)
|
|
crypto_free_shash(ctx->base_hash);
|
|
}
|
|
|
|
static int mv_cra_hash_sha1_init(struct crypto_tfm *tfm)
|
|
{
|
|
return mv_cra_hash_init(tfm, NULL, COP_SHA1, 0);
|
|
}
|
|
|
|
static int mv_cra_hash_hmac_sha1_init(struct crypto_tfm *tfm)
|
|
{
|
|
return mv_cra_hash_init(tfm, "sha1", COP_HMAC_SHA1, SHA1_BLOCK_SIZE);
|
|
}
|
|
|
|
irqreturn_t crypto_int(int irq, void *priv)
|
|
{
|
|
u32 val;
|
|
|
|
val = readl(cpg->reg + SEC_ACCEL_INT_STATUS);
|
|
if (!(val & SEC_INT_ACCEL0_DONE))
|
|
return IRQ_NONE;
|
|
|
|
val &= ~SEC_INT_ACCEL0_DONE;
|
|
writel(val, cpg->reg + FPGA_INT_STATUS);
|
|
writel(val, cpg->reg + SEC_ACCEL_INT_STATUS);
|
|
BUG_ON(cpg->eng_st != ENGINE_BUSY);
|
|
cpg->eng_st = ENGINE_W_DEQUEUE;
|
|
wake_up_process(cpg->queue_th);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
struct crypto_alg mv_aes_alg_ecb = {
|
|
.cra_name = "ecb(aes)",
|
|
.cra_driver_name = "mv-ecb-aes",
|
|
.cra_priority = 300,
|
|
.cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC,
|
|
.cra_blocksize = 16,
|
|
.cra_ctxsize = sizeof(struct mv_ctx),
|
|
.cra_alignmask = 0,
|
|
.cra_type = &crypto_ablkcipher_type,
|
|
.cra_module = THIS_MODULE,
|
|
.cra_init = mv_cra_init,
|
|
.cra_u = {
|
|
.ablkcipher = {
|
|
.min_keysize = AES_MIN_KEY_SIZE,
|
|
.max_keysize = AES_MAX_KEY_SIZE,
|
|
.setkey = mv_setkey_aes,
|
|
.encrypt = mv_enc_aes_ecb,
|
|
.decrypt = mv_dec_aes_ecb,
|
|
},
|
|
},
|
|
};
|
|
|
|
struct crypto_alg mv_aes_alg_cbc = {
|
|
.cra_name = "cbc(aes)",
|
|
.cra_driver_name = "mv-cbc-aes",
|
|
.cra_priority = 300,
|
|
.cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | CRYPTO_ALG_ASYNC,
|
|
.cra_blocksize = AES_BLOCK_SIZE,
|
|
.cra_ctxsize = sizeof(struct mv_ctx),
|
|
.cra_alignmask = 0,
|
|
.cra_type = &crypto_ablkcipher_type,
|
|
.cra_module = THIS_MODULE,
|
|
.cra_init = mv_cra_init,
|
|
.cra_u = {
|
|
.ablkcipher = {
|
|
.ivsize = AES_BLOCK_SIZE,
|
|
.min_keysize = AES_MIN_KEY_SIZE,
|
|
.max_keysize = AES_MAX_KEY_SIZE,
|
|
.setkey = mv_setkey_aes,
|
|
.encrypt = mv_enc_aes_cbc,
|
|
.decrypt = mv_dec_aes_cbc,
|
|
},
|
|
},
|
|
};
|
|
|
|
struct ahash_alg mv_sha1_alg = {
|
|
.init = mv_hash_init,
|
|
.update = mv_hash_update,
|
|
.final = mv_hash_final,
|
|
.finup = mv_hash_finup,
|
|
.digest = mv_hash_digest,
|
|
.halg = {
|
|
.digestsize = SHA1_DIGEST_SIZE,
|
|
.base = {
|
|
.cra_name = "sha1",
|
|
.cra_driver_name = "mv-sha1",
|
|
.cra_priority = 300,
|
|
.cra_flags =
|
|
CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK,
|
|
.cra_blocksize = SHA1_BLOCK_SIZE,
|
|
.cra_ctxsize = sizeof(struct mv_tfm_hash_ctx),
|
|
.cra_init = mv_cra_hash_sha1_init,
|
|
.cra_exit = mv_cra_hash_exit,
|
|
.cra_module = THIS_MODULE,
|
|
}
|
|
}
|
|
};
|
|
|
|
struct ahash_alg mv_hmac_sha1_alg = {
|
|
.init = mv_hash_init,
|
|
.update = mv_hash_update,
|
|
.final = mv_hash_final,
|
|
.finup = mv_hash_finup,
|
|
.digest = mv_hash_digest,
|
|
.setkey = mv_hash_setkey,
|
|
.halg = {
|
|
.digestsize = SHA1_DIGEST_SIZE,
|
|
.base = {
|
|
.cra_name = "hmac(sha1)",
|
|
.cra_driver_name = "mv-hmac-sha1",
|
|
.cra_priority = 300,
|
|
.cra_flags =
|
|
CRYPTO_ALG_ASYNC | CRYPTO_ALG_NEED_FALLBACK,
|
|
.cra_blocksize = SHA1_BLOCK_SIZE,
|
|
.cra_ctxsize = sizeof(struct mv_tfm_hash_ctx),
|
|
.cra_init = mv_cra_hash_hmac_sha1_init,
|
|
.cra_exit = mv_cra_hash_exit,
|
|
.cra_module = THIS_MODULE,
|
|
}
|
|
}
|
|
};
|
|
|
|
static int mv_probe(struct platform_device *pdev)
|
|
{
|
|
struct crypto_priv *cp;
|
|
struct resource *res;
|
|
int irq;
|
|
int ret;
|
|
|
|
if (cpg) {
|
|
printk(KERN_ERR MV_CESA "Second crypto dev?\n");
|
|
return -EEXIST;
|
|
}
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
|
|
if (!res)
|
|
return -ENXIO;
|
|
|
|
cp = kzalloc(sizeof(*cp), GFP_KERNEL);
|
|
if (!cp)
|
|
return -ENOMEM;
|
|
|
|
spin_lock_init(&cp->lock);
|
|
crypto_init_queue(&cp->queue, 50);
|
|
cp->reg = ioremap(res->start, resource_size(res));
|
|
if (!cp->reg) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram");
|
|
if (!res) {
|
|
ret = -ENXIO;
|
|
goto err_unmap_reg;
|
|
}
|
|
cp->sram_size = resource_size(res);
|
|
cp->max_req_size = cp->sram_size - SRAM_CFG_SPACE;
|
|
cp->sram = ioremap(res->start, cp->sram_size);
|
|
if (!cp->sram) {
|
|
ret = -ENOMEM;
|
|
goto err_unmap_reg;
|
|
}
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0 || irq == NO_IRQ) {
|
|
ret = irq;
|
|
goto err_unmap_sram;
|
|
}
|
|
cp->irq = irq;
|
|
|
|
platform_set_drvdata(pdev, cp);
|
|
cpg = cp;
|
|
|
|
cp->queue_th = kthread_run(queue_manag, cp, "mv_crypto");
|
|
if (IS_ERR(cp->queue_th)) {
|
|
ret = PTR_ERR(cp->queue_th);
|
|
goto err_unmap_sram;
|
|
}
|
|
|
|
ret = request_irq(irq, crypto_int, IRQF_DISABLED, dev_name(&pdev->dev),
|
|
cp);
|
|
if (ret)
|
|
goto err_thread;
|
|
|
|
writel(SEC_INT_ACCEL0_DONE, cpg->reg + SEC_ACCEL_INT_MASK);
|
|
writel(SEC_CFG_STOP_DIG_ERR, cpg->reg + SEC_ACCEL_CFG);
|
|
|
|
ret = crypto_register_alg(&mv_aes_alg_ecb);
|
|
if (ret)
|
|
goto err_irq;
|
|
|
|
ret = crypto_register_alg(&mv_aes_alg_cbc);
|
|
if (ret)
|
|
goto err_unreg_ecb;
|
|
|
|
ret = crypto_register_ahash(&mv_sha1_alg);
|
|
if (ret == 0)
|
|
cpg->has_sha1 = 1;
|
|
else
|
|
printk(KERN_WARNING MV_CESA "Could not register sha1 driver\n");
|
|
|
|
ret = crypto_register_ahash(&mv_hmac_sha1_alg);
|
|
if (ret == 0) {
|
|
cpg->has_hmac_sha1 = 1;
|
|
} else {
|
|
printk(KERN_WARNING MV_CESA
|
|
"Could not register hmac-sha1 driver\n");
|
|
}
|
|
|
|
return 0;
|
|
err_unreg_ecb:
|
|
crypto_unregister_alg(&mv_aes_alg_ecb);
|
|
err_irq:
|
|
free_irq(irq, cp);
|
|
err_thread:
|
|
kthread_stop(cp->queue_th);
|
|
err_unmap_sram:
|
|
iounmap(cp->sram);
|
|
err_unmap_reg:
|
|
iounmap(cp->reg);
|
|
err:
|
|
kfree(cp);
|
|
cpg = NULL;
|
|
platform_set_drvdata(pdev, NULL);
|
|
return ret;
|
|
}
|
|
|
|
static int mv_remove(struct platform_device *pdev)
|
|
{
|
|
struct crypto_priv *cp = platform_get_drvdata(pdev);
|
|
|
|
crypto_unregister_alg(&mv_aes_alg_ecb);
|
|
crypto_unregister_alg(&mv_aes_alg_cbc);
|
|
if (cp->has_sha1)
|
|
crypto_unregister_ahash(&mv_sha1_alg);
|
|
if (cp->has_hmac_sha1)
|
|
crypto_unregister_ahash(&mv_hmac_sha1_alg);
|
|
kthread_stop(cp->queue_th);
|
|
free_irq(cp->irq, cp);
|
|
memset(cp->sram, 0, cp->sram_size);
|
|
iounmap(cp->sram);
|
|
iounmap(cp->reg);
|
|
kfree(cp);
|
|
cpg = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver marvell_crypto = {
|
|
.probe = mv_probe,
|
|
.remove = mv_remove,
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "mv_crypto",
|
|
},
|
|
};
|
|
MODULE_ALIAS("platform:mv_crypto");
|
|
|
|
static int __init mv_crypto_init(void)
|
|
{
|
|
return platform_driver_register(&marvell_crypto);
|
|
}
|
|
module_init(mv_crypto_init);
|
|
|
|
static void __exit mv_crypto_exit(void)
|
|
{
|
|
platform_driver_unregister(&marvell_crypto);
|
|
}
|
|
module_exit(mv_crypto_exit);
|
|
|
|
MODULE_AUTHOR("Sebastian Andrzej Siewior <sebastian@breakpoint.cc>");
|
|
MODULE_DESCRIPTION("Support for Marvell's cryptographic engine");
|
|
MODULE_LICENSE("GPL");
|