From d2531b29306383aaf44a91f730ea0e623021a3d1 Mon Sep 17 00:00:00 2001 From: Ricard Rosson Date: Sat, 11 Jul 2026 12:24:27 +0100 Subject: [PATCH] rp23xx: add TRNG driver (hardware /dev/random and /dev/urandom) The RP2350 embeds an Arm CryptoCell-style true random number generator: a sampled ring oscillator conditioned by von-Neumann, CRNGT and autocorrelation health tests, presenting 192 bits in the six EHR_DATA words. Add a polling driver that serves those conditioned bits as /dev/random and /dev/urandom. Entropy is drawn rarely (an mbedtls CTR_DRBG seeds once and reseeds occasionally), so a blocking poll is used rather than interrupt plumbing; the hardware health-test failures discard the block and re-arm the source, so only conditioned entropy is returned. A startup self-test gates the source before it is trusted: it draws a sample of the conditioned output and rejects a stuck source (identical consecutive 192-bit blocks), a constant byte, or an absurd run of equal bytes. On failure the source is marked unhealthy and every read returns -EIO, so an entropy consumer fail-stops rather than minting keys on a broken source. (The statistical quality is covered by the hardware VN/CRNGT/autocorr tests; this catches the gross, silent failure modes.) New CONFIG_RP23XX_TRNG selects ARCH_HAVE_RNG, which switches /dev/urandom from the software xorshift PRNG to the hardware source (backing getrandom() and thus any entropy consumer such as mbedtls). Tested on a Raspberry Pi Pico 2 W: /dev/random and /dev/urandom register, the startup self-test passes, a read returns at real ring- oscillator sampling latency (~10 ms / 32 B), two independent samples differ, and mbedtls seeds its CTR_DRBG from /dev/urandom successfully. Co-Authored-By: Claude Opus 4.8 Signed-off-by: Ricard Rosson --- arch/arm/src/rp23xx/Kconfig | 11 + arch/arm/src/rp23xx/Make.defs | 4 + arch/arm/src/rp23xx/rp23xx_rng.c | 373 +++++++++++++++++++++++++++++++ 3 files changed, 388 insertions(+) create mode 100644 arch/arm/src/rp23xx/rp23xx_rng.c diff --git a/arch/arm/src/rp23xx/Kconfig b/arch/arm/src/rp23xx/Kconfig index cbeba7bbaa7e0..05a21ad7bb405 100644 --- a/arch/arm/src/rp23xx/Kconfig +++ b/arch/arm/src/rp23xx/Kconfig @@ -811,6 +811,17 @@ endif # ADC endif # RP23XX_ADC +config RP23XX_TRNG + bool "Enable TRNG (hardware true random number generator)" + default n + select ARCH_HAVE_RNG + ---help--- + Enable the RP2350 on-chip true random number generator. It serves + conditioned hardware entropy as /dev/random and /dev/urandom + (overriding the software /dev/urandom), which backs getrandom() and + therefore the mbedtls entropy source. Enable this for any workload + that mints keys or signs on the device. + ##################################################################### # CLK_GPOUT Configuration ##################################################################### diff --git a/arch/arm/src/rp23xx/Make.defs b/arch/arm/src/rp23xx/Make.defs index 0042124804909..945c14d256426 100644 --- a/arch/arm/src/rp23xx/Make.defs +++ b/arch/arm/src/rp23xx/Make.defs @@ -68,6 +68,10 @@ ifeq ($(CONFIG_RP23XX_I2C_SLAVE),y) CHIP_CSRCS += rp23xx_i2c_slave.c endif +ifeq ($(CONFIG_RP23XX_TRNG),y) +CHIP_CSRCS += rp23xx_rng.c +endif + ifeq ($(CONFIG_RP23XX_I2S),y) CHIP_CSRCS += rp23xx_i2s.c CHIP_CSRCS += rp23xx_i2s_pio.c diff --git a/arch/arm/src/rp23xx/rp23xx_rng.c b/arch/arm/src/rp23xx/rp23xx_rng.c new file mode 100644 index 0000000000000..a373f5dd1d91e --- /dev/null +++ b/arch/arm/src/rp23xx/rp23xx_rng.c @@ -0,0 +1,373 @@ +/**************************************************************************** + * arch/arm/src/rp23xx/rp23xx_rng.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * The RP2350 embeds an Arm CryptoCell-style true random number generator: + * a ring oscillator is sampled, von-Neumann + CRNGT + autocorrelation + * health tests condition the raw bits, and every 192 conditioned bits + * appear in the six EHR_DATA words. This driver serves those bits directly + * as /dev/random and /dev/urandom (a polling reader - entropy is drawn + * rarely: mbedtls seeds its CTR_DRBG once and reseeds occasionally, so no + * interrupt plumbing is needed). Health-test failures discard the block + * and re-arm the source, so only conditioned entropy is ever returned. + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "arm_internal.h" +#include "hardware/rp23xx_trng.h" +#include "hardware/rp23xx_resets.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define RP23XX_TRNG_EHR_WORDS 6 /* 192-bit EHR_DATA block */ +#define RP23XX_TRNG_EHR_BYTES (RP23XX_TRNG_EHR_WORDS * 4) + +/* ROSC sample count: cycles the source is sampled per conditioned bit. A + * larger value trades throughput for entropy quality; 1000 mirrors the Arm + * CryptoCell reference and the RP2350 bootrom's TRNG use. + */ + +#define RP23XX_TRNG_SAMPLE_CNT 1000 + +/* All health-test error bits in the interrupt status register. */ + +#define RP23XX_TRNG_ISR_ERRORS (RP23XX_TRNG_RNG_ISR_VN_ERR | \ + RP23XX_TRNG_RNG_ISR_CRNGT_ERR | \ + RP23XX_TRNG_RNG_ISR_AUTOCORR_ERR) + +/* Bounded spin waiting for a 192-bit block (each needs ~SAMPLE_CNT source + * cycles; this is generous and only bounds a wedged source). + */ + +#define RP23XX_TRNG_MAX_SPIN 1000000 + +/* Startup health check (a SECOND line of defence behind the CryptoCell's + * own von-Neumann / CRNGT / autocorrelation tests, which run in hardware on + * every block and are already enforced below). We draw a startup sample of + * the CONDITIONED output and reject a stuck, constant or grossly-repetitive + * source before the RNG is trusted. These bounds are deliberately loose - + * they never fire on healthy entropy but catch a dead/wedged source (all + * identical blocks, a constant byte, or an absurd run) that would otherwise + * silently weaken every key and signing nonce derived from it. + */ + +#define RP23XX_TRNG_HEALTH_BLOCKS 8 /* 8 * 24 = 192 conditioned bytes */ +#define RP23XX_TRNG_HEALTH_BYTES (RP23XX_TRNG_HEALTH_BLOCKS * \ + RP23XX_TRNG_EHR_BYTES) +#define RP23XX_TRNG_MAX_BYTE_RUN 6 /* >=6 equal bytes in a row: fail */ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static bool rp23xx_rng_selftest(void); +static ssize_t rp23xx_rng_read(struct file *filep, char *buffer, size_t len); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static mutex_t g_rng_lock = NXMUTEX_INITIALIZER; +static bool g_rng_inited; +static bool g_rng_healthy; /* startup health check passed */ + +static const struct file_operations g_rng_ops = +{ + NULL, /* open */ + NULL, /* close */ + rp23xx_rng_read, /* read */ + NULL, /* write */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rp23xx_rng_enable_source + * + * Description: + * (Re-)start the entropy source from a clean state: clear any pending + * status, reset the source, and enable sampling. + ****************************************************************************/ + +static void rp23xx_rng_enable_source(void) +{ + putreg32(0, RP23XX_TRNG_RND_SOURCE_ENABLE); + putreg32(RP23XX_TRNG_RNG_ICR_EHR_VALID | RP23XX_TRNG_ISR_ERRORS, + RP23XX_TRNG_RNG_ICR); + putreg32(1, RP23XX_TRNG_RND_SOURCE_ENABLE); +} + +/**************************************************************************** + * Name: rp23xx_rng_init + * + * Description: + * Bring the TRNG out of reset and configure the source. Idempotent. + ****************************************************************************/ + +static void rp23xx_rng_init(void) +{ + if (g_rng_inited) + { + return; + } + + /* Deassert the TRNG block reset and wait for it to come ready. */ + + modifyreg32(RP23XX_RESETS_RESET, RP23XX_RESETS_RESET_TRNG, 0); + while ((getreg32(RP23XX_RESETS_RESET_DONE) & + RP23XX_RESETS_RESET_TRNG) == 0) + { + } + + /* Poll-only: mask every TRNG interrupt (status is still observable). */ + + putreg32(RP23XX_TRNG_RNG_IMR_VN_ERR_INT_MASK | + RP23XX_TRNG_RNG_IMR_CRNGT_ERR_INT_MASK | + RP23XX_TRNG_RNG_IMR_AUTOCORR_ERR_INT_MASK | + RP23XX_TRNG_RNG_IMR_EHR_VALID_INT_MASK, + RP23XX_TRNG_RNG_IMR); + + /* Software-reset the core, then configure the ring-oscillator source and + * the per-bit sample count. DEBUG_CONTROL = 0 keeps the von-Neumann, + * CRNGT and autocorrelation health tests ENABLED (no bypass). + */ + + putreg32(1, RP23XX_TRNG_TRNG_SW_RESET); + putreg32(0, RP23XX_TRNG_TRNG_DEBUG_CONTROL); + putreg32(0, RP23XX_TRNG_TRNG_CONFIG); /* ROSC source 0 */ + putreg32(RP23XX_TRNG_SAMPLE_CNT, RP23XX_TRNG_SAMPLE_CNT1); + + rp23xx_rng_enable_source(); + + /* Gate the source on a startup health check. If it fails, leave + * g_rng_healthy false so every read fails and the entropy consumer + * fail-stops loudly rather than minting keys on a broken source. + */ + + g_rng_healthy = rp23xx_rng_selftest(); + if (!g_rng_healthy) + { + _err("ERROR: TRNG startup health check FAILED - refusing entropy\n"); + } + + g_rng_inited = true; +} + +/**************************************************************************** + * Name: rp23xx_rng_collect + * + * Description: + * Wait for and read one 192-bit conditioned entropy block into out[]. + * Health-test failures discard the block and re-arm the source. Returns + * 0 on success, -ETIMEDOUT if the source never produced a valid block. + ****************************************************************************/ + +static int rp23xx_rng_collect(uint32_t out[RP23XX_TRNG_EHR_WORDS]) +{ + int spin; + int i; + + for (spin = 0; spin < RP23XX_TRNG_MAX_SPIN; spin++) + { + uint32_t isr = getreg32(RP23XX_TRNG_RNG_ISR); + + if ((isr & RP23XX_TRNG_ISR_ERRORS) != 0) + { + /* A health test rejected this block: drop it and re-arm. */ + + rp23xx_rng_enable_source(); + continue; + } + + if ((isr & RP23XX_TRNG_RNG_ISR_EHR_VALID) != 0) + { + for (i = 0; i < RP23XX_TRNG_EHR_WORDS; i++) + { + out[i] = getreg32(RP23XX_TRNG_EHR_DATA(i)); + } + + /* Reading EHR_DATA clears validity; re-arm for the next block. */ + + rp23xx_rng_enable_source(); + return 0; + } + } + + return -ETIMEDOUT; +} + +/**************************************************************************** + * Name: rp23xx_rng_selftest + * + * Description: + * Startup quality gate. Draw a sample of the conditioned source and + * reject it if the entropy is obviously broken: identical consecutive + * blocks (a stuck source), a constant byte value, or a run of equal bytes + * beyond RP23XX_TRNG_MAX_BYTE_RUN. Returns true if the source looks + * healthy. These are loose bounds - the hardware VN/CRNGT/autocorr tests + * (enforced in rp23xx_rng_collect) cover the statistical quality; this + * catches the gross, silent failure modes that would otherwise weaken + * every derived key. + ****************************************************************************/ + +static bool rp23xx_rng_selftest(void) +{ + uint32_t sample[RP23XX_TRNG_HEALTH_BLOCKS][RP23XX_TRNG_EHR_WORDS]; + const uint8_t *bytes = (const uint8_t *)sample; + int run = 1; + int i; + + for (i = 0; i < RP23XX_TRNG_HEALTH_BLOCKS; i++) + { + if (rp23xx_rng_collect(sample[i]) < 0) + { + return false; /* source never produced a block */ + } + + /* No two consecutive conditioned blocks may be identical (192-bit + * collision on healthy entropy is ~2^-192; identical means stuck). + */ + + if (i > 0 && + memcmp(sample[i], sample[i - 1], RP23XX_TRNG_EHR_BYTES) == 0) + { + return false; + } + } + + /* Reject a constant byte value and any absurd run of equal bytes. */ + + for (i = 1; i < RP23XX_TRNG_HEALTH_BYTES; i++) + { + if (bytes[i] == bytes[i - 1]) + { + if (++run >= RP23XX_TRNG_MAX_BYTE_RUN) + { + return false; + } + } + else + { + run = 1; + } + } + + return true; +} + +/**************************************************************************** + * Name: rp23xx_rng_read + ****************************************************************************/ + +static ssize_t rp23xx_rng_read(struct file *filep, char *buffer, size_t len) +{ + size_t got = 0; + int ret; + + ret = nxmutex_lock(&g_rng_lock); + if (ret < 0) + { + return ret; + } + + if (!g_rng_healthy) + { + nxmutex_unlock(&g_rng_lock); + return -EIO; /* startup health check failed - fail closed */ + } + + while (got < len) + { + uint32_t block[RP23XX_TRNG_EHR_WORDS]; + size_t n; + + ret = rp23xx_rng_collect(block); + if (ret < 0) + { + break; + } + + n = MIN(len - got, RP23XX_TRNG_EHR_BYTES); + memcpy(buffer + got, block, n); + got += n; + } + + nxmutex_unlock(&g_rng_lock); + + if (got == 0) + { + return ret < 0 ? ret : -EIO; + } + + return got; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: devrandom_register + * + * Description: + * Initialise the TRNG and register /dev/random. Overrides the generic + * software implementation (this arch selects ARCH_HAVE_RNG). + ****************************************************************************/ + +void devrandom_register(void) +{ + rp23xx_rng_init(); + register_driver("/dev/random", &g_rng_ops, 0444, NULL); +} + +/**************************************************************************** + * Name: devurandom_register + * + * Description: + * Initialise the TRNG and register /dev/urandom (same hardware source). + ****************************************************************************/ + +void devurandom_register(void) +{ + rp23xx_rng_init(); + register_driver("/dev/urandom", &g_rng_ops, 0444, NULL); +}