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

Reply via email to