mirror of
https://github.com/ARM-software/arm-trusted-firmware.git
synced 2025-04-16 17:44:19 +00:00

Having RNG_SR_DRDY bit in RNG_SR register does not mean that there are 4 RNG words ready to be read. Add a check on RNG_SR_DRDY between each word reading. Signed-off-by: Gatien Chevallier <gatien.chevallier@foss.st.com> Change-Id: I46af7ca6c0ddbe19540b248365a5016b15b9a707
273 lines
5.5 KiB
C
273 lines
5.5 KiB
C
/*
|
|
* Copyright (c) 2022, STMicroelectronics - All Rights Reserved
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
|
|
#include <arch_helpers.h>
|
|
#include <drivers/clk.h>
|
|
#include <drivers/delay_timer.h>
|
|
#include <drivers/st/stm32_rng.h>
|
|
#include <drivers/st/stm32mp_reset.h>
|
|
#include <lib/mmio.h>
|
|
#include <libfdt.h>
|
|
|
|
#include <platform_def.h>
|
|
|
|
#if STM32_RNG_VER == 2
|
|
#define DT_RNG_COMPAT "st,stm32-rng"
|
|
#endif
|
|
#if STM32_RNG_VER == 4
|
|
#define DT_RNG_COMPAT "st,stm32mp13-rng"
|
|
#endif
|
|
#define RNG_CR 0x00U
|
|
#define RNG_SR 0x04U
|
|
#define RNG_DR 0x08U
|
|
|
|
#define RNG_CR_RNGEN BIT(2)
|
|
#define RNG_CR_IE BIT(3)
|
|
#define RNG_CR_CED BIT(5)
|
|
#define RNG_CR_CLKDIV GENMASK(19, 16)
|
|
#define RNG_CR_CLKDIV_SHIFT 16U
|
|
#define RNG_CR_CONDRST BIT(30)
|
|
|
|
#define RNG_SR_DRDY BIT(0)
|
|
#define RNG_SR_CECS BIT(1)
|
|
#define RNG_SR_SECS BIT(2)
|
|
#define RNG_SR_CEIS BIT(5)
|
|
#define RNG_SR_SEIS BIT(6)
|
|
|
|
#define RNG_TIMEOUT_US 100000U
|
|
#define RNG_TIMEOUT_STEP_US 10U
|
|
|
|
#define TIMEOUT_US_1MS 1000U
|
|
|
|
#define RNG_NIST_CONFIG_A 0x00F40F00U
|
|
#define RNG_NIST_CONFIG_B 0x01801000U
|
|
#define RNG_NIST_CONFIG_C 0x00F00D00U
|
|
#define RNG_NIST_CONFIG_MASK GENMASK(25, 8)
|
|
|
|
#define RNG_MAX_NOISE_CLK_FREQ 48000000U
|
|
|
|
struct stm32_rng_instance {
|
|
uintptr_t base;
|
|
unsigned long clock;
|
|
};
|
|
|
|
static struct stm32_rng_instance stm32_rng;
|
|
|
|
static void seed_error_recovery(void)
|
|
{
|
|
uint8_t i __maybe_unused;
|
|
|
|
/* Recommended by the SoC reference manual */
|
|
mmio_clrbits_32(stm32_rng.base + RNG_SR, RNG_SR_SEIS);
|
|
dmbsy();
|
|
|
|
#if STM32_RNG_VER == 2
|
|
/* No Auto-reset on version 2, need to clean FIFO */
|
|
for (i = 12U; i != 0U; i--) {
|
|
(void)mmio_read_32(stm32_rng.base + RNG_DR);
|
|
}
|
|
|
|
dmbsy();
|
|
#endif
|
|
|
|
if ((mmio_read_32(stm32_rng.base + RNG_SR) & RNG_SR_SEIS) != 0U) {
|
|
ERROR("RNG noise\n");
|
|
panic();
|
|
}
|
|
}
|
|
|
|
static uint32_t stm32_rng_clock_freq_restrain(void)
|
|
{
|
|
unsigned long clock_rate;
|
|
uint32_t clock_div = 0U;
|
|
|
|
clock_rate = clk_get_rate(stm32_rng.clock);
|
|
|
|
/*
|
|
* Get the exponent to apply on the CLKDIV field in RNG_CR register
|
|
* No need to handle the case when clock-div > 0xF as it is physically
|
|
* impossible
|
|
*/
|
|
while ((clock_rate >> clock_div) > RNG_MAX_NOISE_CLK_FREQ) {
|
|
clock_div++;
|
|
}
|
|
|
|
VERBOSE("RNG clk rate : %lu\n", clk_get_rate(stm32_rng.clock) >> clock_div);
|
|
|
|
return clock_div;
|
|
}
|
|
|
|
static int stm32_rng_enable(void)
|
|
{
|
|
uint32_t sr;
|
|
uint64_t timeout;
|
|
uint32_t clock_div __maybe_unused;
|
|
|
|
#if STM32_RNG_VER == 2
|
|
mmio_write_32(stm32_rng.base + RNG_CR, RNG_CR_RNGEN | RNG_CR_CED);
|
|
#endif
|
|
#if STM32_RNG_VER == 4
|
|
/* Reset internal block and disable CED bit */
|
|
clock_div = stm32_rng_clock_freq_restrain();
|
|
|
|
/* Update configuration fields */
|
|
mmio_clrsetbits_32(stm32_rng.base + RNG_CR, RNG_NIST_CONFIG_MASK,
|
|
RNG_NIST_CONFIG_A | RNG_CR_CONDRST | RNG_CR_CED);
|
|
|
|
mmio_clrsetbits_32(stm32_rng.base + RNG_CR, RNG_CR_CLKDIV,
|
|
(clock_div << RNG_CR_CLKDIV_SHIFT));
|
|
|
|
mmio_clrsetbits_32(stm32_rng.base + RNG_CR, RNG_CR_CONDRST, RNG_CR_RNGEN);
|
|
#endif
|
|
timeout = timeout_init_us(RNG_TIMEOUT_US);
|
|
sr = mmio_read_32(stm32_rng.base + RNG_SR);
|
|
while ((sr & RNG_SR_DRDY) == 0U) {
|
|
if (timeout_elapsed(timeout)) {
|
|
WARN("Timeout waiting\n");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
if ((sr & (RNG_SR_SECS | RNG_SR_SEIS)) != 0U) {
|
|
seed_error_recovery();
|
|
timeout = timeout_init_us(RNG_TIMEOUT_US);
|
|
}
|
|
|
|
udelay(RNG_TIMEOUT_STEP_US);
|
|
sr = mmio_read_32(stm32_rng.base + RNG_SR);
|
|
}
|
|
|
|
VERBOSE("Init RNG done\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* stm32_rng_read - Read a number of random bytes from RNG
|
|
* out: pointer to the output buffer
|
|
* size: number of bytes to be read
|
|
* Return 0 on success, non-0 on failure
|
|
*/
|
|
int stm32_rng_read(uint8_t *out, uint32_t size)
|
|
{
|
|
uint8_t *buf = out;
|
|
size_t len = size;
|
|
int nb_tries;
|
|
uint32_t data32;
|
|
int rc = 0;
|
|
unsigned int count;
|
|
|
|
if (stm32_rng.base == 0U) {
|
|
return -EPERM;
|
|
}
|
|
|
|
while (len != 0U) {
|
|
nb_tries = RNG_TIMEOUT_US / RNG_TIMEOUT_STEP_US;
|
|
do {
|
|
uint32_t status = mmio_read_32(stm32_rng.base + RNG_SR);
|
|
|
|
if ((status & (RNG_SR_SECS | RNG_SR_SEIS)) != 0U) {
|
|
seed_error_recovery();
|
|
}
|
|
|
|
udelay(RNG_TIMEOUT_STEP_US);
|
|
nb_tries--;
|
|
if (nb_tries == 0) {
|
|
rc = -ETIMEDOUT;
|
|
goto bail;
|
|
}
|
|
} while ((mmio_read_32(stm32_rng.base + RNG_SR) &
|
|
RNG_SR_DRDY) == 0U);
|
|
|
|
count = 4U;
|
|
while (len != 0U) {
|
|
if ((mmio_read_32(stm32_rng.base + RNG_SR) & RNG_SR_DRDY) == 0U) {
|
|
break;
|
|
}
|
|
|
|
data32 = mmio_read_32(stm32_rng.base + RNG_DR);
|
|
count--;
|
|
|
|
memcpy(buf, &data32, MIN(len, sizeof(uint32_t)));
|
|
buf += MIN(len, sizeof(uint32_t));
|
|
len -= MIN(len, sizeof(uint32_t));
|
|
|
|
if (count == 0U) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bail:
|
|
if (rc != 0) {
|
|
memset(out, 0, buf - out);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* stm32_rng_init: Initialize rng from DT
|
|
* return 0 on success, negative value on failure
|
|
*/
|
|
int stm32_rng_init(void)
|
|
{
|
|
void *fdt;
|
|
struct dt_node_info dt_rng;
|
|
int node;
|
|
|
|
if (stm32_rng.base != 0U) {
|
|
/* Driver is already initialized */
|
|
return 0;
|
|
}
|
|
|
|
if (fdt_get_address(&fdt) == 0) {
|
|
panic();
|
|
}
|
|
|
|
node = dt_get_node(&dt_rng, -1, DT_RNG_COMPAT);
|
|
if (node < 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (dt_rng.status == DT_DISABLED) {
|
|
return 0;
|
|
}
|
|
|
|
assert(dt_rng.base != 0U);
|
|
|
|
stm32_rng.base = dt_rng.base;
|
|
|
|
if (dt_rng.clock < 0) {
|
|
panic();
|
|
}
|
|
|
|
stm32_rng.clock = (unsigned long)dt_rng.clock;
|
|
clk_enable(stm32_rng.clock);
|
|
|
|
if (dt_rng.reset >= 0) {
|
|
int ret;
|
|
|
|
ret = stm32mp_reset_assert((unsigned long)dt_rng.reset,
|
|
TIMEOUT_US_1MS);
|
|
if (ret != 0) {
|
|
panic();
|
|
}
|
|
|
|
udelay(20);
|
|
|
|
ret = stm32mp_reset_deassert((unsigned long)dt_rng.reset,
|
|
TIMEOUT_US_1MS);
|
|
if (ret != 0) {
|
|
panic();
|
|
}
|
|
}
|
|
|
|
return stm32_rng_enable();
|
|
}
|