blob: bb3f50d3aad5311b28aa4d5a33fb2387efa47810 [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright 2018-2019 NXP
*
* Implementation of Hashing functions.
*/
#include <caam_common.h>
#include <caam_hal_ctrl.h>
#include <caam_hash.h>
#include <caam_jr.h>
#include <caam_utils_mem.h>
#include <caam_utils_status.h>
#include <drvcrypt.h>
#include <drvcrypt_hash.h>
#include <kernel/panic.h>
#include <mm/core_memprot.h>
#include <tee/cache.h>
#include <string.h>
#include <utee_defines.h>
static const struct crypto_hash_ops hash_ops;
/*
* Hash Algorithm definition
*/
struct hashalg {
uint32_t type; /* Algo type for operation */
uint8_t size_digest; /* Digest size */
uint8_t size_block; /* Computing block size */
uint8_t size_ctx; /* CAAM Context Register size (8 + digest size) */
};
/* First part CAAM HW Context - message length */
#define HASH_MSG_LEN 8
/*
* Constants definition of the Hash algorithm
*/
static const struct hashalg hash_alg[] = {
{
/* md5 */
.type = OP_ALGO(MD5),
.size_digest = TEE_MD5_HASH_SIZE,
.size_block = TEE_MD5_HASH_SIZE * 4,
.size_ctx = HASH_MSG_LEN + TEE_MD5_HASH_SIZE,
},
{
/* sha1 */
.type = OP_ALGO(SHA1),
.size_digest = TEE_SHA1_HASH_SIZE,
.size_block = TEE_MAX_HASH_SIZE,
.size_ctx = HASH_MSG_LEN + TEE_SHA1_HASH_SIZE,
},
{
/* sha224 */
.type = OP_ALGO(SHA224),
.size_digest = TEE_SHA224_HASH_SIZE,
.size_block = TEE_MAX_HASH_SIZE,
.size_ctx = HASH_MSG_LEN + TEE_SHA256_HASH_SIZE,
},
{
/* sha256 */
.type = OP_ALGO(SHA256),
.size_digest = TEE_SHA256_HASH_SIZE,
.size_block = TEE_MAX_HASH_SIZE,
.size_ctx = HASH_MSG_LEN + TEE_SHA256_HASH_SIZE,
},
{
/* sha384 */
.type = OP_ALGO(SHA384),
.size_digest = TEE_SHA384_HASH_SIZE,
.size_block = TEE_MAX_HASH_SIZE * 2,
.size_ctx = HASH_MSG_LEN + TEE_SHA512_HASH_SIZE,
},
{
/* sha512 */
.type = OP_ALGO(SHA512),
.size_digest = TEE_SHA512_HASH_SIZE,
.size_block = TEE_MAX_HASH_SIZE * 2,
.size_ctx = HASH_MSG_LEN + TEE_SHA512_HASH_SIZE,
},
};
/*
* Maximum number of entry in the descriptor
*/
#define MAX_DESC_ENTRIES 20
/*
* Full hashing data SW context
*/
struct hashdata {
uint32_t *descriptor; /* Job descriptor */
struct caamblock blockbuf; /* Temporary Block buffer */
struct caambuf ctx; /* Hash Context used by the CAAM */
const struct hashalg *alg; /* Reference to the algo constants */
};
/*
* Format the hash context to keep the reference to the
* operation driver
*/
struct crypto_hash {
struct crypto_hash_ctx hash_ctx; /* Crypto Hash API context */
struct hashdata *ctx; /* Hash Context */
};
/*
* Keep the HW hash limit because after the initialization
* of the module, we don't have the CAAM Controller base address
* to call the function returning the HW capacity.
*/
static uint8_t caam_hash_limit;
/*
* Returns the reference to the driver context
*
* @ctx API Context
*/
static struct crypto_hash *to_hash_ctx(struct crypto_hash_ctx *ctx)
{
assert(ctx && ctx->ops == &hash_ops);
return container_of(ctx, struct crypto_hash, hash_ctx);
}
/*
* Free the internal hashing data context
*
* @ctx [in/out] Caller context variable
*/
static void do_free_intern(struct hashdata *ctx)
{
HASH_TRACE("Free Context (0x%p)", ctx);
if (ctx) {
/* Free the descriptor */
caam_free_desc(&ctx->descriptor);
/* Free the Temporary buffer */
caam_free_buf(&ctx->blockbuf.buf);
/* Free the context register */
caam_free_buf(&ctx->ctx);
}
}
/*
* Allocate the internal hashing data context
*
* @ctx [in/out] Caller context variable
*/
static enum caam_status do_allocate_intern(struct hashdata *ctx)
{
TEE_Result ret = CAAM_OUT_MEMORY;
HASH_TRACE("Allocate Context (0x%p)", ctx);
/* Allocate the descriptor */
ctx->descriptor = caam_calloc_desc(MAX_DESC_ENTRIES);
if (!ctx->descriptor) {
HASH_TRACE("Allocation descriptor error");
goto exit_alloc;
}
/* Initialize the block buffer */
ctx->blockbuf.filled = 0;
ctx->blockbuf.max = ctx->alg->size_block;
/* Allocate the CAAM Context register */
ret = caam_calloc_align_buf(&ctx->ctx, ctx->alg->size_ctx);
if (ret != CAAM_NO_ERROR) {
HASH_TRACE("Allocation context register error");
goto exit_alloc;
}
cache_operation(TEE_CACHEFLUSH, ctx->ctx.data, ctx->ctx.length);
/* Ensure buffer length is 0 */
ctx->ctx.length = 0;
exit_alloc:
if (ret != CAAM_NO_ERROR) {
/* Free all data allocated */
do_free_intern(ctx);
}
return ret;
}
/*
* Free the SW hashing data context
*
* @ctx [in/out] Caller context variable
*/
static void do_free(struct crypto_hash_ctx *ctx)
{
struct crypto_hash *hash = to_hash_ctx(ctx);
HASH_TRACE("Free Context (0x%p)", hash->ctx);
if (hash->ctx) {
do_free_intern(hash->ctx);
caam_free(hash->ctx);
}
free(hash);
}
/*
* Initialization of the Hash operation
*
* @ctx Operation Software context
*/
static TEE_Result do_init(struct crypto_hash_ctx *ctx)
{
struct crypto_hash *hash = to_hash_ctx(ctx);
struct hashdata *hashdata = hash->ctx;
HASH_TRACE("Hash Init (0x%p)", hashdata);
if (!hashdata)
return TEE_ERROR_BAD_PARAMETERS;
/* Initialize the block buffer */
hashdata->blockbuf.filled = 0;
/* Ensure Context length is 0 */
hashdata->ctx.length = 0;
return TEE_SUCCESS;
}
/*
* Update the Hash operation
*
* @ctx Operation Software context
* @data Data to hash
* @len Data length
*/
static TEE_Result do_update(struct crypto_hash_ctx *ctx, const uint8_t *data,
size_t len)
{
TEE_Result ret = TEE_ERROR_GENERIC;
enum caam_status retstatus = CAAM_FAILURE;
struct crypto_hash *hash = to_hash_ctx(ctx);
struct hashdata *hashdata = hash->ctx;
const struct hashalg *alg = NULL;
struct caam_jobctx jobctx = {};
uint32_t *desc = NULL;
size_t fullsize = 0;
size_t size_topost = 0;
size_t size_todo = 0;
size_t size_inmade = 0;
size_t inlen = 0;
paddr_t paddr_data = 0;
HASH_TRACE("Hash Update (0x%p)", hashdata);
if ((!data && len) || !hashdata)
return TEE_ERROR_BAD_PARAMETERS;
alg = hashdata->alg;
if (data) {
paddr_data = virt_to_phys((void *)data);
if (!paddr_data) {
HASH_TRACE("Bad input data virtual address");
ret = TEE_ERROR_BAD_PARAMETERS;
goto exit_update;
}
inlen = len;
}
if (!hashdata->ctx.data) {
retstatus = do_allocate_intern(hashdata);
if (retstatus != CAAM_NO_ERROR) {
ret = TEE_ERROR_OUT_OF_MEMORY;
goto exit_update;
}
}
HASH_TRACE("Update Type 0x%" PRIX32 " - Input @%p-%zu", alg->type,
data, inlen);
/* Calculate the total data to be handled */
fullsize = hashdata->blockbuf.filled + inlen;
size_topost = fullsize % alg->size_block;
size_todo = fullsize - size_topost;
size_inmade = inlen - size_topost;
HASH_TRACE("FullSize %zu - posted %zu - todo %zu", fullsize,
size_topost, size_todo);
if (size_todo) {
desc = hashdata->descriptor;
caam_desc_init(desc);
caam_desc_add_word(desc, DESC_HEADER(0));
/* There are blocks to hash - Create the Descriptor */
if (hashdata->ctx.length) {
HASH_TRACE("Update Operation");
/* Algo Operation - Update */
caam_desc_add_word(desc, HASH_UPDATE(alg->type));
/* Running context to restore */
caam_desc_add_word(desc,
LD_NOIMM(CLASS_2, REG_CTX,
hashdata->ctx.length));
caam_desc_add_ptr(desc, hashdata->ctx.paddr);
} else {
HASH_TRACE("Init Operation");
/* Algo Operation - Init */
caam_desc_add_word(desc, HASH_INIT(alg->type));
hashdata->ctx.length = alg->size_ctx;
}
if (hashdata->blockbuf.filled != 0) {
/* Add the temporary buffer */
caam_desc_add_word(desc,
FIFO_LD_EXT(CLASS_2, MSG, NOACTION));
caam_desc_add_ptr(desc, hashdata->blockbuf.buf.paddr);
caam_desc_add_word(desc, hashdata->blockbuf.filled);
/* Clean the circular buffer data to be loaded */
cache_operation(TEE_CACHECLEAN,
hashdata->blockbuf.buf.data,
hashdata->blockbuf.filled);
hashdata->blockbuf.filled = 0;
}
/* Add the input data multiple of blocksize */
caam_desc_add_word(desc, FIFO_LD_EXT(CLASS_2, MSG, LAST_C2));
caam_desc_add_ptr(desc, paddr_data);
caam_desc_add_word(desc, size_inmade);
/* Clean the input data to be loaded */
cache_operation(TEE_CACHECLEAN, (void *)data, size_inmade);
/* Save the running context */
caam_desc_add_word(desc, ST_NOIMM(CLASS_2, REG_CTX,
hashdata->ctx.length));
caam_desc_add_ptr(desc, hashdata->ctx.paddr);
HASH_DUMPDESC(desc);
/* Ensure Context register data are not in cache */
cache_operation(TEE_CACHEINVALIDATE, hashdata->ctx.data,
hashdata->ctx.length);
jobctx.desc = desc;
retstatus = caam_jr_enqueue(&jobctx, NULL);
if (retstatus == CAAM_NO_ERROR) {
ret = TEE_SUCCESS;
HASH_DUMPBUF("CTX", hashdata->ctx.data,
hashdata->ctx.length);
} else {
HASH_TRACE("CAAM Status 0x%08" PRIx32, jobctx.status);
ret = job_status_to_tee_result(jobctx.status);
}
} else {
ret = TEE_SUCCESS;
if (size_topost) {
/* All input data must be saved */
size_inmade = 0;
}
}
if (size_topost && data) {
struct caambuf indata = {
.data = (uint8_t *)data,
.length = inlen
};
HASH_TRACE("Post %zu of input len %zu made %zu", size_topost,
len, size_inmade);
ret = caam_cpy_block_src(&hashdata->blockbuf, &indata,
size_inmade);
}
exit_update:
if (ret != TEE_SUCCESS)
do_free_intern(hashdata);
return ret;
}
/*
* Finalize the Hash operation
*
* @ctx Operation Software context
* @digest [out] Hash digest buffer
* @len Digest buffer length
*/
static TEE_Result do_final(struct crypto_hash_ctx *ctx, uint8_t *digest,
size_t len)
{
TEE_Result ret = TEE_ERROR_GENERIC;
enum caam_status retstatus = CAAM_FAILURE;
struct crypto_hash *hash = to_hash_ctx(ctx);
struct hashdata *hashdata = hash->ctx;
const struct hashalg *alg = NULL;
struct caam_jobctx jobctx = {};
uint32_t *desc = NULL;
int realloc = 0;
struct caambuf digest_align = {};
HASH_TRACE("Hash Final (0x%p)", hashdata);
if (!digest || !len || !hashdata)
return TEE_ERROR_BAD_PARAMETERS;
alg = hashdata->alg;
if (!hashdata->ctx.data) {
retstatus = do_allocate_intern(hashdata);
if (retstatus != CAAM_NO_ERROR) {
ret = TEE_ERROR_OUT_OF_MEMORY;
goto exit_final;
}
}
if (alg->size_digest > len) {
HASH_TRACE("Digest buffer size %" PRId8 " too short (%zu)",
alg->size_digest, len);
retstatus =
caam_alloc_align_buf(&digest_align, alg->size_digest);
if (retstatus != CAAM_NO_ERROR) {
HASH_TRACE("Hash digest reallocation error");
ret = TEE_ERROR_OUT_OF_MEMORY;
goto exit_final;
}
realloc = 1;
} else {
realloc =
caam_set_or_alloc_align_buf(digest, &digest_align, len);
if (realloc == -1) {
HASH_TRACE("Hash digest reallocation error");
ret = TEE_ERROR_OUT_OF_MEMORY;
goto exit_final;
}
}
HASH_TRACE("Final Type 0x%" PRIX32 " - Digest @%p-%zu", alg->type,
digest_align.data, len);
desc = hashdata->descriptor;
caam_desc_init(desc);
/* Set the descriptor Header with length */
caam_desc_add_word(desc, DESC_HEADER(0));
if (hashdata->ctx.length) {
HASH_TRACE("Final Operation");
caam_desc_add_word(desc, HASH_FINAL(alg->type));
/* Running context to restore */
caam_desc_add_word(desc, LD_NOIMM(CLASS_2, REG_CTX,
hashdata->ctx.length));
caam_desc_add_ptr(desc, hashdata->ctx.paddr);
cache_operation(TEE_CACHEINVALIDATE, hashdata->ctx.data,
hashdata->ctx.length);
HASH_DUMPBUF("CTX", hashdata->ctx.data, hashdata->ctx.length);
hashdata->ctx.length = 0;
} else {
HASH_TRACE("Init/Final Operation");
caam_desc_add_word(desc, HASH_INITFINAL(alg->type));
}
HASH_DUMPBUF("Temporary Block", hashdata->blockbuf.buf.data,
hashdata->blockbuf.filled);
caam_desc_add_word(desc, FIFO_LD_EXT(CLASS_2, MSG, LAST_C2));
caam_desc_add_ptr(desc, hashdata->blockbuf.buf.paddr);
caam_desc_add_word(desc, hashdata->blockbuf.filled);
cache_operation(TEE_CACHECLEAN, hashdata->blockbuf.buf.data,
hashdata->blockbuf.filled);
hashdata->blockbuf.filled = 0;
/* Save the final digest */
caam_desc_add_word(desc, ST_NOIMM(CLASS_2, REG_CTX, alg->size_digest));
caam_desc_add_ptr(desc, digest_align.paddr);
HASH_DUMPDESC(desc);
jobctx.desc = desc;
if (digest_align.nocache == 0)
cache_operation(TEE_CACHEFLUSH, digest_align.data,
alg->size_digest);
retstatus = caam_jr_enqueue(&jobctx, NULL);
if (retstatus == CAAM_NO_ERROR) {
/* Ensure that hash data are correct in cache */
if (digest_align.nocache == 0)
cache_operation(TEE_CACHEINVALIDATE, digest_align.data,
alg->size_digest);
if (realloc)
memcpy(digest, digest_align.data, len);
HASH_DUMPBUF("Digest", digest_align.data,
(size_t)alg->size_digest);
ret = TEE_SUCCESS;
} else {
HASH_TRACE("CAAM Status 0x%08" PRIx32, jobctx.status);
ret = job_status_to_tee_result(jobctx.status);
}
exit_final:
if (realloc == 1)
caam_free_buf(&digest_align);
return ret;
}
/*
* Copy Sofware Hashing Context
*
* @dst_ctx [out] Reference the context destination
* @src_ctx Reference the context source
*/
static void do_copy_state(struct crypto_hash_ctx *dst_ctx,
struct crypto_hash_ctx *src_ctx)
{
enum caam_status retstatus = CAAM_FAILURE;
struct crypto_hash *hash_src = to_hash_ctx(src_ctx);
struct crypto_hash *hash_dst = to_hash_ctx(dst_ctx);
struct hashdata *dst = hash_dst->ctx;
struct hashdata *src = hash_src->ctx;
HASH_TRACE("Copy State context (0x%p) to (0x%p)", src, dst);
if (!dst || !src)
panic();
dst->alg = src->alg;
if (!dst->ctx.data) {
retstatus = do_allocate_intern(dst);
if (retstatus != CAAM_NO_ERROR)
panic();
}
memcpy(dst->ctx.data, src->ctx.data, src->ctx.length);
dst->ctx.length = src->ctx.length;
cache_operation(TEE_CACHECLEAN, dst->ctx.data, dst->ctx.length);
if (src->blockbuf.filled) {
struct caambuf srcdata = {
.data = src->blockbuf.buf.data,
.length = src->blockbuf.filled
};
caam_cpy_block_src(&dst->blockbuf, &srcdata, 0);
}
}
/*
* Registration of the hash Driver
*/
static const struct crypto_hash_ops hash_ops = {
.init = do_init,
.update = do_update,
.final = do_final,
.free_ctx = do_free,
.copy_state = do_copy_state,
};
/*
* Allocate the internal hashing data context
*
* @ctx [out] Caller context variable
* @algo Algorithm ID
*/
static TEE_Result caam_hash_allocate(struct crypto_hash_ctx **ctx,
uint32_t algo)
{
struct crypto_hash *hash = NULL;
struct hashdata *hashdata = NULL;
uint8_t hash_id = TEE_ALG_GET_MAIN_ALG(algo);
unsigned int algo_idx = hash_id - TEE_MAIN_ALGO_MD5;
HASH_TRACE("Allocate Context (%p) algo %" PRId16, ctx, algo_idx);
*ctx = NULL;
if (hash_id > caam_hash_limit)
return TEE_ERROR_NOT_IMPLEMENTED;
if (algo_idx > ARRAY_SIZE(hash_alg))
return TEE_ERROR_NOT_IMPLEMENTED;
hash = calloc(1, sizeof(*hash));
if (!hash)
return TEE_ERROR_OUT_OF_MEMORY;
hashdata = caam_calloc(sizeof(*hashdata));
if (!hashdata) {
HASH_TRACE("Allocation Hash data error");
free(hash);
return TEE_ERROR_OUT_OF_MEMORY;
}
HASH_TRACE("Allocated Context (0x%p)", hashdata);
hashdata->alg = &hash_alg[algo_idx];
hash->hash_ctx.ops = &hash_ops;
hash->ctx = hashdata;
*ctx = &hash->hash_ctx;
return TEE_SUCCESS;
}
enum caam_status caam_hash_init(vaddr_t ctrl_addr)
{
enum caam_status retstatus = CAAM_FAILURE;
caam_hash_limit = caam_hal_ctrl_hash_limit(ctrl_addr);
if (caam_hash_limit != UINT8_MAX) {
if (drvcrypt_register_hash(&caam_hash_allocate) == TEE_SUCCESS)
retstatus = CAAM_NO_ERROR;
}
return retstatus;
}