diff --git a/Documentation/platforms/arm64/imx9/index.rst b/Documentation/platforms/arm64/imx9/index.rst index 4b1e5b7412014..935542f51d9d1 100644 --- a/Documentation/platforms/arm64/imx9/index.rst +++ b/Documentation/platforms/arm64/imx9/index.rst @@ -2,6 +2,42 @@ NXP i.MX9 ========= +Peripheral Support +================== + +========== ======= ===================================================== +Peripheral Support Notes +========== ======= ===================================================== +ELE Yes EdgeLock Enclave, used for entropy and secure boot +RNG Yes ``/dev/random`` and ``/dev/urandom``, backed by the ELE +========== ======= ===================================================== + +RNG +--- + +The EdgeLock Enclave contains a true random number generator. ``CONFIG_IMX9_RNG`` +registers it as ``/dev/random`` and ``/dev/urandom``, which is what seeds the +kernel entropy pool from hardware. Without it ``up_randompool_initialize()`` +has no hardware source. + +``CONFIG_IMX9_RNG`` selects ``CONFIG_IMX9_ELE`` and ``CONFIG_ARCH_HAVE_RNG``. +``CONFIG_DEV_RANDOM`` then defaults on; ``CONFIG_DEV_URANDOM`` does not and must +be set explicitly, after which ``CONFIG_DEV_URANDOM_ARCH`` selects the hardware +backend rather than the software PRNG. + +A read is served in blocks, looping until the request is satisfied. Every block +is checked before it is returned, and a failed check is an error rather than a +short read: the buffer must have been written by the ELE, must not be all +zeros, and must differ from the previous block, which is the FIPS 140-2 +continuous test. + +.. code-block:: console + + nsh> hexdump /dev/random -c 32 + /dev/random at 00000000: + 0000: 7c 9a f9 b0 fb 89 be 9d 23 bf 17 28 88 43 67 10 + 0010: c5 79 10 9e d7 be ca 42 2f 51 00 bd bc ce 77 54 + Supported Boards ================ 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..ae2642a3a6583 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,17 +257,27 @@ 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; + for (uint32_t i = 0; i < msg.header.size - 2; i++) { crc ^= msg.data[i]; @@ -424,6 +462,7 @@ int imx9_ele_get_trng_state(void) { struct ele_trng_state *ele_trng = (struct ele_trng_state *)(msg.data + 1); + if (ele_trng->trng_state != ELE_TRNG_STATUS_READY || ele_trng->csal_state != ELE_CSAL_STATUS_READY) { @@ -442,17 +481,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 +526,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 +547,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..4bc20bc656138 --- /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 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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 */