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