This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit f7f8107a0ad1cc07de0b838f9608839191053f72 Author: Royyan Zahir <[email protected]> AuthorDate: Fri Sep 18 22:28:47 2026 +0400 arch/arm64/imx9: add an ELE-backed /dev/random driver. The i.MX9 has a true random number generator behind the EdgeLock Enclave and imx9_ele_get_random() to reach it, but nothing registers a character device for it, so the entropy pool is never seeded from hardware. stm32h7, nrf52, lpc54xx and rp23xx all provide one; imx9 does not. imx9_ele.c was built only for CONFIG_IMX9_BOOTLOADER, putting the enclave out of reach of the application core. It moves behind a new CONFIG_IMX9_ELE that the bootloader selects, so existing configurations build as before. A transfer that never lands is silent, so the buffer is prefilled with a pattern and a block still holding it is refused, as is an all-zero block and, by the FIPS 140-2 continuous test, a repeat of the one before. Compiles for imx93-evk:nsh with CONFIG_IMX9_RNG=y. Signed-off-by: Royyan Zahir <[email protected]> --- arch/arm64/src/imx9/CMakeLists.txt | 10 +- arch/arm64/src/imx9/Kconfig | 15 ++ arch/arm64/src/imx9/Make.defs | 7 + arch/arm64/src/imx9/imx9_rng.c | 283 +++++++++++++++++++++++++++++++++++++ 4 files changed, 314 insertions(+), 1 deletion(-) diff --git a/arch/arm64/src/imx9/CMakeLists.txt b/arch/arm64/src/imx9/CMakeLists.txt index 259e3631ecf..836362f2d67 100644 --- a/arch/arm64/src/imx9/CMakeLists.txt +++ b/arch/arm64/src/imx9/CMakeLists.txt @@ -90,7 +90,15 @@ if(CONFIG_IMX9_FLEXSPI_NOR) endif() if(CONFIG_IMX9_BOOTLOADER) - list(APPEND SRCS imx9_system_ctl.c imx9_trdc.c imx9_ele.c) + list(APPEND SRCS imx9_system_ctl.c imx9_trdc.c) +endif() + +if(CONFIG_IMX9_ELE) + list(APPEND SRCS imx9_ele.c) +endif() + +if(CONFIG_IMX9_RNG) + list(APPEND SRCS imx9_rng.c) endif() if(CONFIG_IMX9_ROMAPI) diff --git a/arch/arm64/src/imx9/Kconfig b/arch/arm64/src/imx9/Kconfig index 8add331ff7a..bfb16597713 100644 --- a/arch/arm64/src/imx9/Kconfig +++ b/arch/arm64/src/imx9/Kconfig @@ -81,6 +81,7 @@ config IMX9_BOOTLOADER bool "Bootloader" select ARM64_DECODEFIQ if ARCH_ARM64_EXCEPTION_LEVEL = 3 select IMX9_DDR_TRAINING if ARCH_ARM64_EXCEPTION_LEVEL = 3 + select IMX9_ELE default n ---help--- Configure NuttX as the bootloader. NuttX will be compiled @@ -125,8 +126,22 @@ config IMX_AHAB_CNTR_ADDR ---help--- Physical address where the AHAB container header is loaded into memory. +config IMX9_ELE + bool + default n + menu "i.MX9 Peripheral Selection" +config IMX9_RNG + bool "ELE true random number generator" + default n + select IMX9_ELE + select ARCH_HAVE_RNG + ---help--- + Register /dev/random and /dev/urandom, both backed by the ELE + true random number generator. Without this the entropy pool is + never seeded from hardware. + config IMX9_EDMA bool "eDMA" default n diff --git a/arch/arm64/src/imx9/Make.defs b/arch/arm64/src/imx9/Make.defs index 021f913e216..914c378764f 100644 --- a/arch/arm64/src/imx9/Make.defs +++ b/arch/arm64/src/imx9/Make.defs @@ -103,9 +103,16 @@ endif ifeq ($(CONFIG_IMX9_BOOTLOADER),y) CHIP_CSRCS += imx9_system_ctl.c CHIP_CSRCS += imx9_trdc.c +endif + +ifeq ($(CONFIG_IMX9_ELE),y) CHIP_CSRCS += imx9_ele.c endif +ifeq ($(CONFIG_IMX9_RNG),y) + CHIP_CSRCS += imx9_rng.c +endif + ifeq ($(CONFIG_IMX9_ROMAPI),y) CHIP_CSRCS += imx9_romapi.c endif diff --git a/arch/arm64/src/imx9/imx9_rng.c b/arch/arm64/src/imx9/imx9_rng.c new file mode 100644 index 00000000000..4bc20bc6561 --- /dev/null +++ b/arch/arm64/src/imx9/imx9_rng.c @@ -0,0 +1,283 @@ +/**************************************************************************** + * arch/arm64/src/imx9/imx9_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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> + +#include <assert.h> +#include <debug.h> +#include <errno.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> + +#include <nuttx/drivers/drivers.h> +#include <nuttx/fs/fs.h> +#include <nuttx/mutex.h> + +#include <chip.h> + +#include "arm64_internal.h" +#include "imx9_ele.h" + +#if defined(CONFIG_DEV_RANDOM) || defined(CONFIG_DEV_URANDOM_ARCH) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#if !defined(ARMV8A_DCACHE_LINESIZE) || ARMV8A_DCACHE_LINESIZE == 0 +# undef ARMV8A_DCACHE_LINESIZE +# define ARMV8A_DCACHE_LINESIZE 64 +#endif + +/* The ELE writes the result by DMA, so the landing buffer is a whole number + * of cache lines and nothing else shares them. + */ + +#define RNG_BLOCKLEN ARMV8A_DCACHE_LINESIZE + +/* Prefilled before every request. Zero could not be told apart from an + * ELE that answered with zeros, so an untouched buffer reports separately. + */ + +#define RNG_FILL 0xaa + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static ssize_t imx9_rng_read(struct file *filep, char *buffer, size_t + buflen); + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct rng_dev_s +{ + mutex_t rd_devlock; /* Exclusive access to the ELE */ + uint8_t rd_lastval[RNG_BLOCKLEN]; /* Previous block, FIPS test */ + bool rd_first; /* No previous block yet */ +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct rng_dev_s g_rngdev = +{ + .rd_devlock = NXMUTEX_INITIALIZER, + .rd_first = true, +}; + +static uint8_t g_rngbuf[RNG_BLOCKLEN] + aligned_data(ARMV8A_DCACHE_LINESIZE); + +static const struct file_operations g_rngops = +{ + NULL, /* open */ + NULL, /* close */ + imx9_rng_read, /* read */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: imx9_rng_all + * + * Description: + * Rejecting a constant block costs nothing, since a genuine one has + * probability 2^-512, and it covers the first block, which the continuous + * test below cannot. + * + ****************************************************************************/ + +static bool imx9_rng_all(const uint8_t *buf, size_t len, uint8_t val) +{ + size_t i; + + for (i = 0; i < len; i++) + { + if (buf[i] != val) + { + return false; + } + } + + return true; +} + +/**************************************************************************** + * Name: imx9_rng_block + * + * Description: + * Fetch one RNG_BLOCKLEN block from the ELE into g_rngbuf and check it. + * + * Returned Value: + * Zero on success, a negated errno on failure. Never returns a block + * that failed a health check. + * + ****************************************************************************/ + +static int imx9_rng_block(void) +{ + int ret; + + memset(g_rngbuf, RNG_FILL, sizeof(g_rngbuf)); + + ret = imx9_ele_get_random(g_rngbuf, sizeof(g_rngbuf)); + if (ret < 0) + { + _err("ERROR: ELE random request failed: %d\n", ret); + return ret; + } + + if (imx9_rng_all(g_rngbuf, sizeof(g_rngbuf), RNG_FILL)) + { + _err("ERROR: buffer untouched; the ELE write never reached here\n"); + return -EIO; + } + + if (imx9_rng_all(g_rngbuf, sizeof(g_rngbuf), 0)) + { + _err("ERROR: ELE returned an all-zero block\n"); + return -EIO; + } + + /* FIPS 140-2 continuous test: a repeat means the source has stalled. */ + + if (g_rngdev.rd_first) + { + g_rngdev.rd_first = false; + } + else if (memcmp(g_rngdev.rd_lastval, g_rngbuf, sizeof(g_rngbuf)) == 0) + { + _err("ERROR: ELE repeated a block\n"); + return -EIO; + } + + memcpy(g_rngdev.rd_lastval, g_rngbuf, sizeof(g_rngbuf)); + return OK; +} + +/**************************************************************************** + * Name: imx9_rng_read + ****************************************************************************/ + +static ssize_t imx9_rng_read(struct file *filep, char *buffer, size_t buflen) +{ + size_t done = 0; + int ret; + + ret = nxmutex_lock(&g_rngdev.rd_devlock); + if (ret < 0) + { + return ret; + } + + while (done < buflen) + { + size_t chunk = buflen - done; + + ret = imx9_rng_block(); + if (ret < 0) + { + /* A short read is a lie about how much entropy the caller got, so + * report the failure unless some was already delivered. + */ + + nxmutex_unlock(&g_rngdev.rd_devlock); + return done > 0 ? (ssize_t)done : ret; + } + + if (chunk > sizeof(g_rngbuf)) + { + chunk = sizeof(g_rngbuf); + } + + memcpy(buffer + done, g_rngbuf, chunk); + done += chunk; + } + + /* Leave nothing behind for the next caller to find. */ + + memset(g_rngbuf, 0, sizeof(g_rngbuf)); + + nxmutex_unlock(&g_rngdev.rd_devlock); + return (ssize_t)done; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: devrandom_register + * + * Description: + * Register the /dev/random driver, backed by the ELE true random number + * generator. Must be called BEFORE devurandom_register. + * + * Input Parameters: + * None + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_DEV_RANDOM +void devrandom_register(void) +{ + register_driver("/dev/random", &g_rngops, 0444, NULL); +} +#endif + +/**************************************************************************** + * Name: devurandom_register + * + * Description: + * Register /dev/urandom. The ELE is the source for both nodes: it is a + * hardware generator, so there is nothing weaker to offer here. + * + * Input Parameters: + * None + * + * Returned Value: + * None + * + ****************************************************************************/ + +#ifdef CONFIG_DEV_URANDOM_ARCH +void devurandom_register(void) +{ + register_driver("/dev/urandom", &g_rngops, 0444, NULL); +} +#endif + +#endif /* CONFIG_DEV_RANDOM || CONFIG_DEV_URANDOM_ARCH */
