blob: fb5e3de49d35e4b9b18c58d2b79a194f2c5120aa [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/**
* Copyright 2017-2019 NXP
*
* Brief CAAM Random Number Generator manager.
* Implementation of RNG functions.
*/
#include <atomic.h>
#include <caam_common.h>
#include <caam_hal_rng.h>
#include <caam_jr.h>
#include <caam_rng.h>
#include <caam_utils_mem.h>
#include <crypto/crypto.h>
#include <kernel/panic.h>
#include <mm/core_memprot.h>
#include <rng_support.h>
#include <tee/cache.h>
#include <string.h>
/*
* Define the RNG Data buffer size and number
*/
#define RNG_DATABUF_SIZE 1024
#define RNG_DATABUF_NB 2
/*
* Define the number of descriptor entry to generate random data
*/
#define RNG_GEN_DESC_ENTRIES 5
/*
* Status of the data generation
*/
enum rngsta {
DATA_EMPTY = 0, /* Data bufer empty */
DATA_ONGOING, /* Data generation on going */
DATA_FAILURE, /* Error during data generation */
DATA_OK, /* Data generation complete with success */
};
/*
* RNG Data generation
*/
struct rngdata {
struct caam_jobctx jobctx; /* Job Ring Context */
uint32_t job_id; /* Job Id enqueued */
uint8_t *data; /* Random Data buffer */
size_t size; /* Size in bytes of the Random data buffer */
size_t rdindex; /* Current data index in the buffer */
enum rngsta status; /* Status of the data generation */
};
/*
* RNG module private data
*/
struct rng_privdata {
vaddr_t baseaddr; /* RNG base address */
bool instantiated; /* RNG instantiated */
struct rngdata databuf[RNG_DATABUF_NB]; /* RNG Data generation */
uint8_t dataidx; /* Current RNG Data buffer */
};
static struct rng_privdata *rng_privdata;
/* Allocate and initialize module private data */
static enum caam_status do_allocate(void)
{
struct rngdata *rngdata = NULL;
unsigned int idx = 0;
/* Allocate the Module resources */
rng_privdata = caam_calloc(sizeof(*rng_privdata));
if (!rng_privdata) {
RNG_TRACE("Private Data allocation error");
return CAAM_OUT_MEMORY;
}
rng_privdata->instantiated = false;
/* Allocates the RNG Data Buffers */
for (idx = 0; idx < RNG_DATABUF_NB; idx++) {
rngdata = &rng_privdata->databuf[idx];
rngdata->data = caam_calloc_align(RNG_DATABUF_SIZE);
if (!rngdata->data)
return CAAM_OUT_MEMORY;
rngdata->size = RNG_DATABUF_SIZE;
rngdata->jobctx.desc = caam_calloc_desc(RNG_GEN_DESC_ENTRIES);
if (!rngdata->jobctx.desc)
return CAAM_OUT_MEMORY;
}
return CAAM_NO_ERROR;
}
/* Free module private data */
static void do_free(void)
{
struct rngdata *rng = NULL;
unsigned int idx = 0;
if (rng_privdata) {
for (idx = 0; idx < RNG_DATABUF_NB; idx++) {
rng = &rng_privdata->databuf[idx];
/* Check if there is a Job ongoing to cancel it */
if (atomic_load_u32(&rng->status) == DATA_ONGOING)
caam_jr_cancel(rng->job_id);
caam_free_desc(&rng->jobctx.desc);
caam_free(rng->data);
rng->data = NULL;
}
caam_free(rng_privdata);
rng_privdata = NULL;
}
}
#ifdef CFG_NXP_CAAM_RNG_DRV
/*
* RNG data generation job ring callback completion
*
* @jobctx RNG data JR Job Context
*/
static void rng_data_done(struct caam_jobctx *jobctx)
{
struct rngdata *rng = jobctx->context;
RNG_TRACE("RNG Data id 0x%08" PRIx32 " done with status 0x%" PRIx32,
rng->job_id, jobctx->status);
if (JRSTA_SRC_GET(jobctx->status) == JRSTA_SRC(NONE)) {
atomic_store_u32(&rng->status, DATA_OK);
/* Invalidate the data buffer to ensure software gets it */
cache_operation(TEE_CACHEINVALIDATE, rng->data, rng->size);
} else {
RNG_TRACE("RNG Data completion in error 0x%" PRIx32,
jobctx->status);
atomic_store_u32(&rng->status, DATA_FAILURE);
}
rng->job_id = 0;
rng->rdindex = 0;
}
/*
* Prepares the data generation descriptors
*
* @rng Reference to the RNG Data object
*/
static enum caam_status prepare_gen_desc(struct rngdata *rng)
{
paddr_t paddr = 0;
uint32_t *desc = NULL;
/* Convert the buffer virtual address to physical address */
paddr = virt_to_phys(rng->data);
if (!paddr)
return CAAM_FAILURE;
desc = rng->jobctx.desc;
caam_desc_init(desc);
caam_desc_add_word(desc, DESC_HEADER(0));
caam_desc_add_word(desc, RNG_GEN_DATA);
caam_desc_add_word(desc, FIFO_ST(RNG_TO_MEM, rng->size));
caam_desc_add_ptr(desc, paddr);
RNG_DUMPDESC(desc);
/* Prepare the job context */
rng->jobctx.context = rng;
rng->jobctx.callback = rng_data_done;
return CAAM_NO_ERROR;
}
/*
* Launches a RNG Data generation
*
* @rng RNG Data context
*/
static enum caam_status do_rng_start(struct rngdata *rng)
{
enum caam_status ret = CAAM_FAILURE;
/* Ensure that data buffer is visible from the HW */
cache_operation(TEE_CACHEFLUSH, rng->data, rng->size);
rng->job_id = 0;
atomic_store_u32(&rng->status, DATA_EMPTY);
ret = caam_jr_enqueue(&rng->jobctx, &rng->job_id);
if (ret == CAAM_PENDING) {
atomic_store_u32(&rng->status, DATA_ONGOING);
ret = CAAM_NO_ERROR;
} else {
RNG_TRACE("RNG Job Ring Error 0x%08x", ret);
atomic_store_u32(&rng->status, DATA_FAILURE);
ret = CAAM_FAILURE;
}
return ret;
}
/* Checks if there are random data available */
static enum caam_status do_check_data(void)
{
enum caam_status ret = CAAM_FAILURE;
struct rngdata *rng = NULL;
uint32_t wait_jobs = 0;
unsigned int idx = 0;
unsigned int loop = 4;
/* Check if there is a RNG Job to be run */
for (idx = 0; idx < RNG_DATABUF_NB; idx++) {
rng = &rng_privdata->databuf[idx];
if (atomic_load_u32(&rng->status) == DATA_EMPTY) {
RNG_TRACE("Start RNG #%" PRIu32 " data generation",
idx);
ret = do_rng_start(rng);
if (ret != CAAM_NO_ERROR)
return CAAM_FAILURE;
}
}
/* Check if the current data buffer contains data */
rng = &rng_privdata->databuf[rng_privdata->dataidx];
switch (atomic_load_u32(&rng->status)) {
case DATA_OK:
return CAAM_NO_ERROR;
default:
/* Wait until one of the data buffer completes */
do {
wait_jobs = 0;
for (idx = 0; idx < RNG_DATABUF_NB; idx++) {
rng = &rng_privdata->databuf[idx];
wait_jobs |= rng->job_id;
if (atomic_load_u32(&rng->status) == DATA_OK) {
RNG_TRACE("RNG Data buffer #%" PRIu32
" ready",
idx);
rng_privdata->dataidx = idx;
return CAAM_NO_ERROR;
}
}
if (!wait_jobs) {
RNG_TRACE("There are no Data Buffers ongoing");
return CAAM_FAILURE;
}
/* Need to wait until one of the jobs completes */
(void)caam_jr_dequeue(wait_jobs, 100);
} while (loop--);
break;
}
return CAAM_FAILURE;
}
/*
* Return the requested random data
*
* @buf [out] data buffer
* @len number of bytes to returns
*/
static TEE_Result do_rng_read(uint8_t *buf, size_t len)
{
struct rngdata *rng = NULL;
size_t remlen = len;
uint8_t *rngbuf = buf;
if (!rng_privdata) {
RNG_TRACE("RNG Driver not initialized");
return TEE_ERROR_BAD_STATE;
}
if (!rng_privdata->instantiated) {
RNG_TRACE("RNG Driver not initialized");
return TEE_ERROR_BAD_STATE;
}
do {
if (do_check_data() != CAAM_NO_ERROR) {
RNG_TRACE("No Data available or Error");
return TEE_ERROR_BAD_STATE;
}
rng = &rng_privdata->databuf[rng_privdata->dataidx];
RNG_TRACE("Context #%" PRIu8
" contains %zu data asked %zu (%zu)",
rng_privdata->dataidx, rng->size - rng->rdindex,
remlen, len);
/* Check that current context data are available */
if ((rng->size - rng->rdindex) <= remlen) {
/*
* There is no or just enough data available,
* copy all data
*/
RNG_TRACE("Copy all available data");
memcpy(rngbuf, &rng->data[rng->rdindex],
rng->size - rng->rdindex);
remlen -= rng->size - rng->rdindex;
rngbuf += rng->size - rng->rdindex;
/* Set the RNG data status as empty */
atomic_store_u32(&rng->status, DATA_EMPTY);
} else {
/* There is enough data in the current context */
RNG_TRACE("Copy %zu data", remlen);
memcpy(rngbuf, &rng->data[rng->rdindex], remlen);
rng->rdindex += remlen;
remlen = 0;
}
} while (remlen);
return TEE_SUCCESS;
}
/* Initialize the RNG module to generate data */
static enum caam_status caam_rng_init_data(void)
{
enum caam_status retstatus = CAAM_FAILURE;
struct rngdata *rng = NULL;
unsigned int idx = 0;
for (idx = 0; idx < RNG_DATABUF_NB; idx++) {
rng = &rng_privdata->databuf[idx];
retstatus = prepare_gen_desc(rng);
if (retstatus != CAAM_NO_ERROR)
break;
}
return retstatus;
}
#endif /* CFG_NXP_CAAM_RNG_DRV */
/*
* Prepares the instantiation descriptor
*
* @nb_sh Number of the State Handle
* @sh_status State Handles status
* @desc Reference to the descriptor
* @desc [out] Descriptor filled
*/
static void prepare_inst_desc(uint32_t nb_sh, uint32_t sh_status,
uint32_t *desc)
{
bool key_loaded = false;
unsigned int sh_idx = 0;
unsigned int nb_max_sh = nb_sh;
/* Read the SH and secure key status */
key_loaded = caam_hal_rng_key_loaded(rng_privdata->baseaddr);
RNG_TRACE("RNG SH Status 0x%08" PRIx32 " - Key Status %" PRId8,
sh_status, key_loaded);
while (sh_status & BIT(sh_idx))
sh_idx++;
RNG_TRACE("Instantiation start at SH%" PRIu32 " (%" PRIu32 ")", sh_idx,
nb_max_sh);
/* Don't set the descriptor header now */
caam_desc_init(desc);
caam_desc_add_word(desc, DESC_HEADER(0));
/* First State Handle to instantiate */
caam_desc_add_word(desc, RNG_SH_INST(sh_idx));
/* Next State Handles */
for (sh_idx++; sh_idx < nb_max_sh; sh_idx++) {
if (!(sh_status & BIT(sh_idx))) {
/*
* If there is more SH to instantiate, add a wait loop
* followed by a reset of the done status to execute
* next command
*/
caam_desc_add_word(desc,
JUMP_C1_LOCAL(ALL_COND_TRUE,
JMP_COND(NONE), 1));
caam_desc_add_word(desc,
LD_NOCLASS_IMM(REG_CLEAR_WRITTEN,
sizeof(uint32_t)));
caam_desc_add_word(desc, 0x1);
caam_desc_add_word(desc, RNG_SH_INST(sh_idx));
}
}
/* Load the Key if needed */
if (!key_loaded) {
/*
* Add a wait loop while previous operation not completed,
* followed by a register clear before executing next command
*/
caam_desc_add_word(desc, JUMP_C1_LOCAL(ALL_COND_TRUE,
JMP_COND(NONE), 1));
caam_desc_add_word(desc, LD_NOCLASS_IMM(REG_CLEAR_WRITTEN,
sizeof(uint32_t)));
caam_desc_add_word(desc, 0x1);
caam_desc_add_word(desc, RNG_GEN_SECKEYS);
}
RNG_DUMPDESC(desc);
}
enum caam_status caam_rng_instantiation(void)
{
enum caam_status retstatus = CAAM_FAILURE;
struct caam_jobctx jobctx = {};
uint32_t *desc = NULL;
uint32_t sh_status = 0;
uint32_t nb_sh = 0;
uint32_t sh_mask = 0;
uint32_t inc_delay = 0;
RNG_TRACE("RNG Instantation");
/* Check if RNG is already instantiated */
if (caam_hal_rng_instantiated(rng_privdata->baseaddr)) {
RNG_TRACE("RNG already instantiated");
retstatus = CAAM_NO_ERROR;
goto end_inst;
}
/*
* RNG needs to be instantiated. Allocate and prepare the
* Job Descriptor
*/
/* Calculate the State Handles bit mask */
nb_sh = caam_hal_rng_get_nb_sh(rng_privdata->baseaddr);
sh_mask = GENMASK_32(nb_sh - 1, 0);
/*
* The maximum size of the descriptor is:
* |----------------------|
* | Header | = 1
* |----------------------|
* | First instantation | = 1
* |----------------------|-------------------------
* | wait complete | = 1
* |----------------------|
* | Clear done status | Repeat (nb_sh - 1)
* | | = 2
* |----------------------|
* | next SH instantation | = 1
* |----------------------|-------------------------
* | wait complete | = 1
* |----------------------|
* | Clear done status | = 2
* | |
* |----------------------|
* | Generate Secure Keys | = 1
* |----------------------|
* | Pad with a 0 | = 1
*/
desc = caam_calloc_desc(2 + (nb_sh - 1) * 4 + 4 + 1);
if (!desc) {
RNG_TRACE("Descriptor Allocation error");
retstatus = CAAM_OUT_MEMORY;
goto end_inst;
}
jobctx.desc = desc;
do {
/* Check if all State Handles are instantiated */
sh_status = caam_hal_rng_get_sh_status(rng_privdata->baseaddr);
if ((sh_status & sh_mask) == sh_mask) {
RNG_TRACE("RNG All SH are instantiated (0x%08" PRIx32
")",
sh_status);
retstatus = CAAM_NO_ERROR;
goto end_inst;
}
if (sh_status == 0) {
retstatus = caam_hal_rng_kick(rng_privdata->baseaddr,
inc_delay);
RNG_TRACE("RNG Kick (inc=%" PRIu32 ") ret 0x%08x",
inc_delay, retstatus);
if (retstatus != CAAM_NO_ERROR) {
retstatus = CAAM_FAILURE;
goto end_inst;
}
inc_delay += 200;
}
prepare_inst_desc(nb_sh, sh_status, desc);
retstatus = caam_jr_enqueue(&jobctx, NULL);
RNG_TRACE("RNG Job returned 0x%08x", retstatus);
if (retstatus != CAAM_NO_ERROR &&
retstatus != CAAM_JOB_STATUS)
goto end_inst;
if (retstatus == CAAM_JOB_STATUS) {
RNG_TRACE("RNG Job status 0x%08" PRIx32, jobctx.status);
if ((JRSTA_SRC_GET(jobctx.status) != JRSTA_SRC(CCB)) ||
(JRSTA_CCB_GET_ERR(jobctx.status) !=
(JRSTA_CCB_CHAID_RNG | JRSTA_CCB_ERRID_HW)))
retstatus = CAAM_FAILURE;
else
retstatus = CAAM_NO_ERROR;
}
} while (retstatus == CAAM_NO_ERROR);
end_inst:
if (retstatus == CAAM_NO_ERROR)
rng_privdata->instantiated = true;
caam_free_desc(&desc);
RNG_TRACE("RNG Instantiation return 0x%08x", retstatus);
return retstatus;
}
enum caam_status caam_rng_init(vaddr_t ctrl_addr)
{
enum caam_status retstatus = CAAM_FAILURE;
RNG_TRACE("Initialization");
retstatus = do_allocate();
if (retstatus == CAAM_NO_ERROR) {
rng_privdata->baseaddr = ctrl_addr;
retstatus = caam_rng_instantiation();
}
#ifdef CFG_NXP_CAAM_RNG_DRV
if (retstatus == CAAM_NO_ERROR)
retstatus = caam_rng_init_data();
#endif
if (retstatus != CAAM_NO_ERROR)
do_free();
return retstatus;
}
#ifdef CFG_NXP_CAAM_RNG_DRV
TEE_Result crypto_rng_read(void *buf, size_t blen)
{
if (!buf)
return TEE_ERROR_BAD_PARAMETERS;
return do_rng_read(buf, blen);
}
uint8_t hw_get_random_byte(void)
{
uint8_t data = 0;
if (do_rng_read(&data, 1) != TEE_SUCCESS)
panic();
return data;
}
#endif