| // 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 |