diff options
Diffstat (limited to '')
-rw-r--r-- | drivers/st/crypto/stm32_rng.c | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/drivers/st/crypto/stm32_rng.c b/drivers/st/crypto/stm32_rng.c new file mode 100644 index 0000000..1342fd4 --- /dev/null +++ b/drivers/st/crypto/stm32_rng.c @@ -0,0 +1,273 @@ +/* + * 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(); +} |