From fc50a34f2681ace0a0e4f0ec7f9baf8642abb406 Mon Sep 17 00:00:00 2001 From: Royyan Zahir Date: Sat, 19 Sep 2026 06:36:54 +0400 Subject: [PATCH 1/3] arch/arm64: implement up_addrenv_va_to_pa(). up_addrenv_va_to_pa() is declared in include/nuttx/arch.h but implemented only by armv7-a, so no arm64 port can map a virtual address to a physical one. A driver whose device addresses memory physically has nothing to call. The translation is asked of the MMU with AT S1E1R rather than walked in software, so it answers for whatever is actually mapped: any granule size, block or page, at any level, and it cannot drift from the tables in use. PAR_EL1 is one register per CPU, so nothing may run between the translation and reading the result. Interrupts are banked with it, so masking them locally is sufficient and SMP needs nothing further. Returns zero for an address that is not mapped for a privileged read, which is what the declaration in arch.h specifies. Note this differs from the armv7-a implementation, which returns the virtual address unchanged. Signed-off-by: Royyan Zahir --- arch/arm64/src/common/CMakeLists.txt | 6 ++ arch/arm64/src/common/Make.defs | 6 ++ arch/arm64/src/common/arm64_physpgaddr.c | 101 +++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 arch/arm64/src/common/arm64_physpgaddr.c diff --git a/arch/arm64/src/common/CMakeLists.txt b/arch/arm64/src/common/CMakeLists.txt index 02c301031d26e..670c9f37a0aa8 100644 --- a/arch/arm64/src/common/CMakeLists.txt +++ b/arch/arm64/src/common/CMakeLists.txt @@ -73,6 +73,12 @@ endif() if(CONFIG_ARCH_HAVE_MMU) list(APPEND SRCS arm64_mmu.c) + + # drivers/misc/addrenv.c defines the same entry points from a table. + + if(NOT CONFIG_DEV_SIMPLE_ADDRENV) + list(APPEND SRCS arm64_physpgaddr.c) + endif() endif() if(CONFIG_ARM64_MTE) diff --git a/arch/arm64/src/common/Make.defs b/arch/arm64/src/common/Make.defs index f41ed4f096aff..90d2f89e36e53 100644 --- a/arch/arm64/src/common/Make.defs +++ b/arch/arm64/src/common/Make.defs @@ -80,6 +80,12 @@ endif ifeq ($(CONFIG_ARCH_HAVE_MMU),y) CMN_CSRCS += arm64_mmu.c + +# drivers/misc/addrenv.c defines the same entry points from a table. + +ifneq ($(CONFIG_DEV_SIMPLE_ADDRENV),y) +CMN_CSRCS += arm64_physpgaddr.c +endif endif ifeq ($(CONFIG_ARM64_MTE),y) diff --git a/arch/arm64/src/common/arm64_physpgaddr.c b/arch/arm64/src/common/arm64_physpgaddr.c new file mode 100644 index 0000000000000..9180387fdcc4f --- /dev/null +++ b/arch/arm64/src/common/arm64_physpgaddr.c @@ -0,0 +1,101 @@ +/**************************************************************************** + * arch/arm64/src/common/arm64_physpgaddr.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 + +#include + +#include +#include + +#include "arm64_arch.h" +#include "arm64_internal.h" + +#ifndef CONFIG_DEV_SIMPLE_ADDRENV + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* PAR_EL1, Physical Address Register. Bit 0 reports a failed translation; + * on success bits [51:12] carry the physical frame. + */ + +#define PAR_F (1ull << 0) +#define PAR_PA_MASK (0x000ffffffffff000ull) +#define VA_PAGE_OFFSET_MASK (0xfffull) + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_addrenv_va_to_pa + * + * Description: + * Map a virtual address to its physical address. + * + * The translation is asked of the MMU rather than walked in software, so + * it answers for whatever is actually mapped: any granule size, block or + * page, at any level, and it cannot drift from the tables in use. + * + * Input Parameters: + * va - The virtual address to be mapped. + * + * Returned Value: + * Physical address on success; zero if the address is not mapped for a + * privileged read. + * + ****************************************************************************/ + +uintptr_t up_addrenv_va_to_pa(void *va) +{ + irqstate_t flags; + uint64_t par; + + /* PAR_EL1 is a single register per CPU, so nothing may run between the + * translation and reading the result or it reads someone else's answer. + * Interrupts are banked with it, so masking them locally is enough. + */ + + flags = up_irq_save(); + + __asm__ volatile ("at s1e1r, %0" : : "r" (va) : "memory"); + UP_ISB(); + par = read_sysreg(par_el1); + + up_irq_restore(flags); + + if ((par & PAR_F) != 0) + { + return 0; + } + + return (uintptr_t)((par & PAR_PA_MASK) | + ((uintptr_t)va & VA_PAGE_OFFSET_MASK)); +} + +#endif /* CONFIG_DEV_SIMPLE_ADDRENV */ From 7da388a95e578ff8e6dbdd5c0c980f49f913269c Mon Sep 17 00:00:00 2001 From: Royyan Zahir Date: Sat, 19 Sep 2026 22:24:20 +0400 Subject: [PATCH 2/3] arch/arm64/imx9: give the ELE a physical address and the cache a virtual one. The ELE addresses memory physically; cache maintenance takes a virtual address. Both buffer calls supply one and use it for both, in opposite directions: get_random() runs up_flush_dcache() on a physical address, get_key() hands the enclave a virtual one. Both fail silently, and both are correct only while the two are equal. Take the virtual address in both, maintain the cache on it, and translate for the message. get_random() also gains the alignment check get_key() already has. Signed-off-by: Royyan Zahir --- arch/arm64/src/imx9/imx9_ele.c | 82 +++++++++++++++++++++++++++++----- arch/arm64/src/imx9/imx9_ele.h | 2 +- 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/arch/arm64/src/imx9/imx9_ele.c b/arch/arm64/src/imx9/imx9_ele.c index 17bbd0eca7d08..6be1d1f718362 100644 --- a/arch/arm64/src/imx9/imx9_ele.c +++ b/arch/arm64/src/imx9/imx9_ele.c @@ -24,6 +24,7 @@ * Included Files ****************************************************************************/ +#include #include #include @@ -146,6 +147,30 @@ static void imx9_ele_receivemsg(struct ele_msg *msg_ptr) } } +/**************************************************************************** + * Name: imx9_ele_buffer_pa + * + * Description: + * Physical address of a buffer the ELE will read or write. The enclave + * addresses memory physically while cache maintenance takes the virtual + * address, so a caller holding one of them cannot supply the other. + * + * Returned Value: + * The physical address, or zero if the buffer is not mapped. + * + ****************************************************************************/ + +static uintptr_t imx9_ele_buffer_pa(void *va) +{ +#ifdef CONFIG_ARCH_USE_MMU + return up_addrenv_va_to_pa(va); +#else + /* Without translation the virtual address is the physical one. */ + + return (uintptr_t)va; +#endif +} + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -199,6 +224,9 @@ uint32_t imx9_ele_read_common_fuse(uint32_t fuse_id) int imx9_ele_get_key(uint8_t *key, size_t key_size, uint8_t *ctx, size_t ctx_size) { + uintptr_t key_pa; + uintptr_t ctx_pa; + if (!key) { _err("Invalid key parameter\n"); @@ -229,14 +257,23 @@ int imx9_ele_get_key(uint8_t *key, size_t key_size, return -EINVAL; } + key_pa = imx9_ele_buffer_pa(key); + ctx_pa = imx9_ele_buffer_pa(ctx); + + if (key_pa == 0 || ctx_pa == 0) + { + _err("Buffer is not mapped\n"); + return -EFAULT; + } + msg.header.version = ELE_VERSION; msg.header.tag = ELE_CMD_TAG; msg.header.size = 7; msg.header.command = ELE_DERIVE_KEY_REQ; - msg.data[0] = upper_32_bits((ulong)key); - msg.data[1] = lower_32_bits((ulong)key); - msg.data[2] = upper_32_bits((ulong)ctx); - msg.data[3] = lower_32_bits((ulong)ctx); + msg.data[0] = upper_32_bits((ulong)key_pa); + msg.data[1] = lower_32_bits((ulong)key_pa); + msg.data[2] = upper_32_bits((ulong)ctx_pa); + msg.data[3] = lower_32_bits((ulong)ctx_pa); msg.data[4] = ((ctx_size << 16) | key_size); uint32_t crc = msg.header.data; @@ -442,17 +479,39 @@ int imx9_ele_get_trng_state(void) return -EIO; } -int imx9_ele_get_random(uint32_t paddr, size_t len) +int imx9_ele_get_random(void *buf, size_t len) { uint16_t counter = 0; uint16_t max_tries = ELE_RNG_TIMEOUT_US / ELE_RNG_SLEEP_US; + uintptr_t paddr; - if (paddr == 0 || len == 0) + if (buf == NULL || len == 0) { _err("Wrong input parameters!\n"); return -EINVAL; } + /* The buffer is invalidated after the transfer, so anything sharing its + * first or last cache line would lose whatever was written meanwhile. + */ + + if (!IS_ALIGNED((uintptr_t)buf, ARMV8A_DCACHE_LINESIZE) || + !IS_ALIGNED(len, ARMV8A_DCACHE_LINESIZE)) + { + _err("Buffer is not a whole number of cache lines\n"); + return -EINVAL; + } + + paddr = imx9_ele_buffer_pa(buf); + + /* The address travels in a single 32-bit message word. */ + + if (paddr == 0 || paddr > UINT32_MAX - len) + { + _err("Buffer is not mapped, or is beyond the ELE address range\n"); + return -EFAULT; + } + while ((imx9_ele_get_trng_state() != 0)) { if (counter > max_tries) @@ -465,16 +524,18 @@ int imx9_ele_get_random(uint32_t paddr, size_t len) counter++; } - /* Flush the cache before sending the request to ELE. */ + /* Cache maintenance takes the virtual address; the ELE takes the + * physical one. + */ - up_flush_dcache((uintptr_t)paddr, (uintptr_t)(paddr + len)); + up_flush_dcache((uintptr_t)buf, (uintptr_t)buf + len); msg.header.version = ELE_VERSION_FW; msg.header.tag = ELE_CMD_TAG; msg.header.size = 4; msg.header.command = ELE_GET_RNG_REQ; msg.data[0] = 0; - msg.data[1] = paddr; + msg.data[1] = (uint32_t)paddr; msg.data[2] = len; imx9_ele_sendmsg(&msg); @@ -484,8 +545,7 @@ int imx9_ele_get_random(uint32_t paddr, size_t len) { /* Invalidate the cache so we can read the result from RAM. */ - up_invalidate_dcache((uintptr_t)paddr, - (uintptr_t)(paddr + len)); + up_invalidate_dcache((uintptr_t)buf, (uintptr_t)buf + len); return 0; } diff --git a/arch/arm64/src/imx9/imx9_ele.h b/arch/arm64/src/imx9/imx9_ele.h index f2335883daddb..b28cdd0dcd0ce 100644 --- a/arch/arm64/src/imx9/imx9_ele.h +++ b/arch/arm64/src/imx9/imx9_ele.h @@ -266,7 +266,7 @@ int imx9_ele_get_trng_state(void); * ****************************************************************************/ -int imx9_ele_get_random(uint32_t paddr, size_t len); +int imx9_ele_get_random(void *buf, size_t len); /**************************************************************************** * Name: imx9_ele_commit From 0e7024cfca0e5fdaf7c3164e448cb524cff2758a Mon Sep 17 00:00:00 2001 From: Royyan Zahir Date: Fri, 18 Sep 2026 22:28:47 +0400 Subject: [PATCH 3/3] 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 --- 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 | 285 +++++++++++++++++++++++++++++ 4 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 arch/arm64/src/imx9/imx9_rng.c diff --git a/arch/arm64/src/imx9/CMakeLists.txt b/arch/arm64/src/imx9/CMakeLists.txt index 259e3631ecfd8..836362f2d67b0 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 8add331ff7a3c..bfb1659771311 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 021f913e21614..914c378764f9a 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 0000000000000..e6a0ebd7623c9 --- /dev/null +++ b/arch/arm64/src/imx9/imx9_rng.c @@ -0,0 +1,285 @@ +/**************************************************************************** + * 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 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "arm64_internal.h" +#include "imx9_ele.h" + +#if defined(CONFIG_IMX9_RNG) +#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 */ +#endif /* CONFIG_IMX9_RNG */