diff --git a/arch.mk b/arch.mk index 3ad87647..43f55caa 100644 --- a/arch.mk +++ b/arch.mk @@ -284,6 +284,9 @@ ifeq ($(ARCH),ARM) ifeq ($(TARGET),imx_rt) CORTEX_M7=1 endif + ifeq ($(TARGET),imx95_m7) + CORTEX_M7=1 + endif ifeq ($(TARGET),stm32l0) CORTEX_M0=1 SPI_TARGET=stm32 diff --git a/config/examples/imx95-m7.config b/config/examples/imx95-m7.config new file mode 100644 index 00000000..cc7232b7 --- /dev/null +++ b/config/examples/imx95-m7.config @@ -0,0 +1,35 @@ +ARCH?=ARM +TARGET?=imx95_m7 +SIGN?=ECC256 +HASH?=SHA256 +DEBUG?=0 +VTOR?=1 +CORTEX_M0?=0 +NO_ASM?=0 +NO_MPU?=1 +EXT_FLASH?=0 +SPI_FLASH?=0 +ALLOW_DOWNGRADE?=0 +NVM_FLASH_WRITEONCE?=0 +WOLFBOOT_VERSION?=0 +V?=0 +SPMATH?=1 +RAM_CODE?=1 +DUALBANK_SWAP?=0 +PKA?=0 + +# wolfBoot runs from ITCM; the partitions live in the M7's reserved DDR window +# (memory@80000000, 16 MiB) where Linux stages images before restarting the +# core. See hal/imx95_m7.c for why "flash" here is ordinary DDR. +WOLFBOOT_PARTITION_SIZE?=0x400000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x80100000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x80500000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x80900000 + +# Sized for ML-DSA-87 from the start, even though bring-up signs with ECC256, +# so switching to SIGN=ML_DSA is a config change rather than a re-layout. +# ML-DSA-87: 4627-byte signature, 2592-byte public key. +WOLFBOOT_SECTOR_SIZE?=0x4000 +IMAGE_HEADER_SIZE?=12288 + +WOLFBOOT_SMALL_STACK?=0 diff --git a/hal/imx95_m7.c b/hal/imx95_m7.c new file mode 100644 index 00000000..2a527529 --- /dev/null +++ b/hal/imx95_m7.c @@ -0,0 +1,290 @@ +/* imx95_m7.c + * + * HAL for the Cortex-M7 on the NXP i.MX95. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* The M7 on this part has no dedicated flash. wolfBoot is loaded into TCM by + * the Linux remoteproc driver on the A55 cluster, and the images it verifies + * live in the DDR region reserved for the M7 (memory@80000000, 16 MiB). The + * "flash" the partitions sit in is therefore ordinary DDR: writes are copies + * and erases are fills. Updates are staged by Linux writing the update + * partition before restarting the core. + * + * Addresses come from the i.MX 95 Reference Manual rev 5 ch. 123 and from the + * board's own device tree; see BOARD-FINDINGS.md in the demo tree. + * + * ITCM core view 0x00000000 system view 0x203C0000 256 KiB + * DTCM core view 0x20000000 system view 0x20400000 256 KiB + * DDR 0x80000000 16 MiB + * + * The core/system distinction matters: wolfBoot is linked for the core view, + * while remoteproc loads through the system view and the imx_rproc driver + * translates between them. + */ + +#include +#include +#include +#include +#include "image.h" + +/* --------------------------------------------------------------------------- + * remoteproc resource table + * + * Linux finds the resource table by looking for a .resource_table section in + * the ELF *it* loads. On this target that ELF is wolfBoot, not the payload, so + * the vdev/vring declaration has to live here even though the RPMsg endpoint is + * serviced later by whatever wolfBoot boots. Without it the kernel logs + * "No resource table in elf" and never creates the virtio device. + * + * The addresses are the ones the board's device tree already reserves, so the + * two sides agree by construction rather than by convention: + * + * vdev0vring0 0x88000000 32 KiB + * vdev0vring1 0x88008000 32 KiB + * vdevbuffer 0x88020000 1 MiB + * + * A vring of 256 buffers at 4 KiB alignment needs about 10 KiB, so each fits + * its 32 KiB reservation with room to spare. + * ------------------------------------------------------------------------- */ + +#define RSC_VDEV 3U +#define VIRTIO_ID_RPMSG 7U +#define VIRTIO_RPMSG_F_NS (1U << 0) /* name service announcements */ + +#define RSC_VRING0_DA 0x88000000U +#define RSC_VRING1_DA 0x88008000U +#define RSC_VRING_ALIGN 0x1000U +#define RSC_VRING_NUM 256U + +struct fw_rsc_vdev_vring { + uint32_t da; + uint32_t align; + uint32_t num; + uint32_t notifyid; + uint32_t reserved; +}; + +struct fw_rsc_vdev { + uint32_t type; + uint32_t id; + uint32_t notifyid; + uint32_t dfeatures; + uint32_t gfeatures; + uint32_t config_len; + uint8_t status; + uint8_t num_of_vrings; + uint8_t reserved[2]; + struct fw_rsc_vdev_vring vring[2]; +}; + +struct imx95_rsc_table { + uint32_t ver; + uint32_t num; + uint32_t reserved[2]; + uint32_t offset[1]; + struct fw_rsc_vdev vdev; +}; + +const struct imx95_rsc_table resource_table + __attribute__((section(".resource_table"), used)) = { + 1, /* ver */ + 1, /* num: one entry */ + { 0, 0 }, /* reserved */ + { offsetof(struct imx95_rsc_table, vdev) }, + { + RSC_VDEV, + VIRTIO_ID_RPMSG, + 0, /* notifyid, assigned by the host */ + VIRTIO_RPMSG_F_NS, /* dfeatures */ + 0, /* gfeatures, filled by the host */ + 0, /* config_len */ + 0, /* status, driven by the host */ + 2, /* num_of_vrings */ + { 0, 0 }, + { + { RSC_VRING0_DA, RSC_VRING_ALIGN, RSC_VRING_NUM, 0, 0 }, + { RSC_VRING1_DA, RSC_VRING_ALIGN, RSC_VRING_NUM, 1, 0 } + } + } +}; + +/* Erased state of the DDR-backed pseudo-flash. */ +#define FLASH_ERASED_BYTE 0xFFU + +/* Bounds of the M7's reserved DDR window. Anything outside it is rejected + * rather than silently corrupting memory that belongs to Linux. */ +#define M7_DDR_BASE 0x80000000UL +#define M7_DDR_SIZE 0x01000000UL + +/* Liveness marker. The M7 has no confirmed UART route on this carrier, so + * early bring-up reports progress through shared memory that Linux can read + * with devmem instead of over a serial port. + * + * This lives inside the M7's own DDR window, immediately above the console + * ring at 0x80F00000, and deliberately NOT in the 0x88000000 block: the device + * tree reserves that for RPMsg (vdev0/vdev1 vrings, and a 1 MiB vdevbuffer at + * 0x88020000), so a status word there would be scribbling on buffers Linux + * owns as soon as RPMsg is brought up. */ +#define WOLFBOOT_STATUS_ADDR 0x80F10000UL +#define WOLFBOOT_STATUS_MAGIC 0x57424F54UL /* "WBOT" */ + +/* Cortex-M7 DWT cycle counter. With no usable console on this carrier the + * cycle counter is how the signature-verification cost gets measured: it is + * exact, free to read, and needs no peripheral. Timestamps are published + * alongside the status word for the A55 to read. */ +#define CORE_DEMCR (*(volatile uint32_t *)0xE000EDFCUL) +#define DEMCR_TRCENA (1UL << 24) +#define DWT_CTRL (*(volatile uint32_t *)0xE0001000UL) +#define DWT_CYCCNTENA (1UL << 0) +#define DWT_CYCCNT (*(volatile uint32_t *)0xE0001004UL) + +/* Cortex-M7 cache maintenance. Both caches are OFF out of reset, and this + * image runs its text from DDR, so leaving them off means every instruction + * fetch and data access goes to external memory - measured at ~664 cycles per + * byte for SHA-256, roughly fifty times slower than the core is capable of. + * + * The ARMv7-M default memory map already marks 0x80000000-0x9FFFFFFF as normal + * cacheable memory, so no MPU region is needed to make this take effect. */ +#define SCB_CCR (*(volatile uint32_t *)0xE000ED14UL) +#define SCB_CCSIDR (*(volatile uint32_t *)0xE000ED80UL) +#define SCB_CSSELR (*(volatile uint32_t *)0xE000ED84UL) +#define SCB_ICIALLU (*(volatile uint32_t *)0xE000EF50UL) +#define SCB_DCISW (*(volatile uint32_t *)0xE000EF60UL) +#define CCR_IC (1UL << 17) +#define CCR_DC (1UL << 16) + +#define DSB() __asm__ volatile ("dsb 0xF" ::: "memory") +#define ISB() __asm__ volatile ("isb 0xF" ::: "memory") + +static void hal_cache_enable(void) +{ + uint32_t ccsidr, sets, ways, s, w; + + /* I-cache: invalidate, then enable. */ + DSB(); ISB(); + SCB_ICIALLU = 0UL; + DSB(); ISB(); + SCB_CCR |= CCR_IC; + DSB(); ISB(); + + /* D-cache: invalidate every set/way before enabling, otherwise stale + * lines from reset can be written back over live DDR. */ + SCB_CSSELR = 0UL; /* select L1 data cache */ + DSB(); + ccsidr = SCB_CCSIDR; + sets = (ccsidr >> 13) & 0x7FFFUL; + ways = (ccsidr >> 3) & 0x3FFUL; + for (s = 0; s <= sets; s++) { + for (w = 0; w <= ways; w++) { + /* DCISW: way in [31:30], set in [13:5] for a 32-byte line. */ + SCB_DCISW = ((w << 30) | (s << 5)); + } + } + DSB(); + SCB_CCR |= CCR_DC; + DSB(); ISB(); +} + +static void hal_cycle_counter_start(void) +{ + CORE_DEMCR |= DEMCR_TRCENA; + DWT_CYCCNT = 0; + DWT_CTRL |= DWT_CYCCNTENA; +} + +static int hal_addr_is_valid(uint32_t address, int len) +{ + uint64_t start = (uint64_t)address; + uint64_t end; + + if (len < 0) + return 0; + end = start + (uint64_t)len; + if (start < (uint64_t)M7_DDR_BASE) + return 0; + if (end > (uint64_t)M7_DDR_BASE + (uint64_t)M7_DDR_SIZE) + return 0; + return 1; +} + +#ifdef __WOLFBOOT + +/* Record a progress code where the A55 side can see it. Kept deliberately + * trivial: no locking, no ordering guarantees beyond the write itself. */ +void hal_status(uint32_t code) +{ + volatile uint32_t *status = (volatile uint32_t *)WOLFBOOT_STATUS_ADDR; + + status[0] = WOLFBOOT_STATUS_MAGIC; + status[1] = code; + /* status[2] is stamped at hal_init, status[3] at hal_prepare_boot; the + * difference is the cost of everything wolfBoot does in between, which is + * dominated by the signature verification. */ + status[code + 1] = DWT_CYCCNT; +} + +void hal_init(void) +{ + /* Clocks, DDR and the TCM split are configured before this core is + * released: DDR by the OEI/SPL stage, the TCM split by the System Manager + * on the M33 via M7_CFG[TCM_SIZE] in BLK_CTRL_Secure_AON. There is + * nothing for wolfBoot to bring up here except the caches, which are off + * out of reset and which this image needs because it runs from DDR. */ + hal_cache_enable(); + hal_cycle_counter_start(); + hal_status(1); +} + +void hal_prepare_boot(void) +{ + hal_status(2); +} + +#endif /* __WOLFBOOT */ + +int RAMFUNCTION hal_flash_write(uint32_t address, const uint8_t *data, int len) +{ + if (data == NULL) + return -1; + if (!hal_addr_is_valid(address, len)) + return -1; + + memcpy((void *)(uintptr_t)address, data, (size_t)len); + return 0; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ +} + +void RAMFUNCTION hal_flash_lock(void) +{ +} + +int RAMFUNCTION hal_flash_erase(uint32_t address, int len) +{ + if (!hal_addr_is_valid(address, len)) + return -1; + + memset((void *)(uintptr_t)address, FLASH_ERASED_BYTE, (size_t)len); + return 0; +} diff --git a/hal/imx95_m7.ld b/hal/imx95_m7.ld new file mode 100644 index 00000000..f9843830 --- /dev/null +++ b/hal/imx95_m7.ld @@ -0,0 +1,72 @@ +/* Linker script for wolfBoot on the i.MX95 Cortex-M7. + * + * Linked for the *core* view of TCM. The Linux remoteproc driver loads through + * the system view (ITCM 0x203C0000, DTCM 0x20400000 at the default 256K/256K + * TCM split) and imx_rproc translates between the two. Linking for the system + * view produces an image that loads cleanly and faults on the first fetch. + * + * Sizes assume M7_CFG[TCM_SIZE] = 000b (256 KiB ITCM + 256 KiB DTCM), which is + * the reset default. The System Manager on the M33 owns that field. + */ + +MEMORY +{ + ITCM (rx) : ORIGIN = 0x00000000, LENGTH = 256K + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 256K +} + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.isr_vector)) + . = ALIGN(8); + *(.text*) + *(.rodata*) + *(.init*) + *(.fini*) + . = ALIGN(4); + _end_text = .; + } > ITCM + + /* Consumed by the remoteproc ELF loader. Not required to start the core, + * but RPMsg needs it, so keep it even when empty. */ + .resource_table : + { + . = ALIGN(8); + KEEP(*(.resource_table*)) + . = ALIGN(8); + } > ITCM + + .edidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > ITCM + + _stored_data = .; + + .data : AT (_stored_data) + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + _end_data = .; + } > DTCM + + .bss (NOLOAD) : + { + _start_bss = .; + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + __bss_end__ = .; + _end = .; + } > DTCM + . = ALIGN(4); +} + +END_STACK = ORIGIN(DTCM) + LENGTH(DTCM); diff --git a/hal/uart/uart_drv_imx95_m7.c b/hal/uart/uart_drv_imx95_m7.c new file mode 100644 index 00000000..bb2995bb --- /dev/null +++ b/hal/uart/uart_drv_imx95_m7.c @@ -0,0 +1,106 @@ +/* uart_drv_imx95_m7.c + * + * Console for the Cortex-M7 on the NXP i.MX95. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* This is a shared-memory console, not a peripheral driver. + * + * The M7's LPUART is not routed to an accessible header on the Toradex SMARC + * Development Board: markers written to /dev/ttyLP1..3 from Linux appear on no + * host serial channel, and Linux owns all four LPUART instances anyway, so an + * M7 writing to one would contend with it. Rather than depend on unproven pin + * routing, the console is a ring buffer in the M7's own reserved DDR window, + * which the A55 cluster reads with the memtool helper. + * + * The buffer sits at the top of the M7's 16 MiB carveout (memory@80000000), + * clear of the wolfBoot partitions lower down (BOOT 0x80100000, UPDATE + * 0x80500000, SWAP 0x80900000). It deliberately does NOT use the RPMsg + * vdevbuffer carveout, which belongs to the RPMsg transport. + * + * Reader contract: `wr` counts bytes ever written and never wraps within a run, + * so a reader tracks its own position and copies (wr - pos) bytes from + * data[pos % size]. Overrun is possible and expected for very chatty output; + * the reader detects it when (wr - pos) > size and reports the gap rather than + * silently printing corrupt text. + */ + +#include + +#define CONSOLE_BASE 0x80F00000UL +#define CONSOLE_MAGIC 0x4E4F4357UL /* "WCON" little-endian */ +#define CONSOLE_SIZE 0x10000UL /* 64 KiB of text */ + +struct console_hdr { + volatile uint32_t magic; + volatile uint32_t wr; /* total bytes written, monotonic */ + volatile uint32_t size; /* == CONSOLE_SIZE */ + volatile uint32_t rsvd; +}; + +#define CONSOLE_HDR ((struct console_hdr *)CONSOLE_BASE) +#define CONSOLE_DATA ((volatile uint8_t *)(CONSOLE_BASE + sizeof(struct console_hdr))) + +int uart_init(uint32_t bitrate, uint8_t data, char parity, uint8_t stop) +{ + struct console_hdr *h = CONSOLE_HDR; + + (void)bitrate; + (void)data; + (void)parity; + (void)stop; + + h->wr = 0; + h->size = CONSOLE_SIZE; + /* Magic last: a reader that samples mid-init sees no valid console rather + * than a valid magic with a stale size. */ + h->magic = CONSOLE_MAGIC; + return 0; +} + +int uart_tx(const uint8_t c) +{ + struct console_hdr *h = CONSOLE_HDR; + uint32_t w; + + if (h->magic != CONSOLE_MAGIC) + (void)uart_init(0, 0, 'N', 0); + + w = h->wr; + CONSOLE_DATA[w % CONSOLE_SIZE] = c; + /* Publish the byte before advancing the counter, so a reader never sees a + * count covering a byte that has not been stored yet. */ + h->wr = w + 1; + return 0; +} + +int uart_rx(uint8_t *c) +{ + (void)c; + return -1; /* console is write-only */ +} + +void uart_write(const char *buf, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i++) + (void)uart_tx((const uint8_t)buf[i]); +} diff --git a/test-app/ARM-imx95_m7.ld b/test-app/ARM-imx95_m7.ld new file mode 100644 index 00000000..28abd89f --- /dev/null +++ b/test-app/ARM-imx95_m7.ld @@ -0,0 +1,89 @@ +/* Linker script for the wolfBoot test app on the i.MX95 Cortex-M7. + * + * The generic ARM.ld is not usable here: it gives RAM only 16 KiB and defines + * no heap symbols, and wolfCrypt's test/benchmark need both a real heap (RSA + * and ECC key material) and syscalls.c's _sbrk, which references + * _Min_Heap_Size. + * + * Code runs from the BOOT partition in the M7's reserved DDR window; data and + * heap live in DTCM (core view 0x20000000, 256 KiB at the reset-default + * M7_CFG[TCM_SIZE] split). Keeping the working set in TCM is the point of the + * exercise -- these numbers are meant to characterise the real-time core, not + * DDR latency. + */ + +_Min_Heap_Size = 0x00018000; /* 96 KiB - RSA/ECC test vectors and key structs */ +_Min_Stack_Size = 0x00008000; /* 32 KiB */ + +MEMORY +{ + FLASH (rx) : ORIGIN = @WOLFBOOT_TEST_APP_ADDRESS@, LENGTH = @WOLFBOOT_TEST_APP_SIZE@ + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 256K /* DTCM, core view */ +} + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.isr_vector)) + *(.init) + *(.fini) + *(.text*) + KEEP(*(.rodata*)) + . = ALIGN(4); + _end_text = .; + } > FLASH + + .ARM : + { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } > FLASH + + _stored_data = .; + + .data : AT (_stored_data) + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > RAM + + .bss : + { + _start_bss = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + _end = .; + } > RAM + + /* Reserve heap and stack explicitly so the link fails loudly if DTCM is + * over-committed, rather than overflowing at run time. */ + .heap (NOLOAD) : + { + . = ALIGN(8); + PROVIDE ( _start_heap = . ); + . = . + _Min_Heap_Size; + . = ALIGN(8); + } > RAM + + .stack (NOLOAD) : + { + . = . + _Min_Stack_Size; + . = ALIGN(8); + PROVIDE ( END_STACK = . ); + PROVIDE ( _end_stack = . ); + } > RAM +} + +_wolfboot_partition_boot_address = @WOLFBOOT_PARTITION_BOOT_ADDRESS@; +_wolfboot_partition_size = @WOLFBOOT_PARTITION_SIZE@; +_wolfboot_partition_update_address = @WOLFBOOT_PARTITION_UPDATE_ADDRESS@; +_wolfboot_partition_swap_address = @WOLFBOOT_PARTITION_SWAP_ADDRESS@; diff --git a/test-app/Makefile b/test-app/Makefile index 6019c0a9..0f233bbc 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -616,6 +616,16 @@ ifeq ($(TARGET),stm32l5) APP_OBJS+=../hal/uart/uart_drv_$(UART_TARGET).o endif +# i.MX95 Cortex-M7. The console is a shared-memory ring buffer rather than an +# LPUART: no M7 UART is routed to a host-accessible channel on the Toradex SMARC +# carrier, and Linux owns all four LPUART instances. -mcpu/-mfpu come from +# arch.mk via CORTEX_M7. +ifeq ($(TARGET),imx95_m7) + CFLAGS+=-I.. + APP_OBJS+=../hal/uart/uart_drv_$(UART_TARGET).o + LSCRIPT_TEMPLATE=ARM-imx95_m7.ld +endif + ifeq ($(TARGET),stm32h5) ifeq ($(TZEN),1) LSCRIPT_TEMPLATE=ARM-stm32h5-ns.ld diff --git a/test-app/app_imx95_m7.c b/test-app/app_imx95_m7.c new file mode 100644 index 00000000..86c55d16 --- /dev/null +++ b/test-app/app_imx95_m7.c @@ -0,0 +1,113 @@ +/* app_imx95_m7.c + * + * Test application for the Cortex-M7 on the NXP i.MX95. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfBoot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Output goes to the shared-memory console in hal/uart/uart_drv_imx95_m7.c, + * because this part has no M7 UART routed to a host-accessible channel on the + * Toradex SMARC carrier. wolfCrypt's test and benchmark call printf(), which + * test-app/syscalls.c routes to uart_write(), so their output lands there too. + * + * Liveness markers stay in a shared status block so the demo's right pane can + * poll them cheaply without parsing console text: + * + * devmem 0x80F10010 -> 0x41505031 ("APP1") once the app is running + * devmem 0x80F10014 -> heartbeat, increments continuously + * + * wolfBoot writes its own progress codes at 0x80F10000, so a reader can tell + * "bootloader ran" from "payload ran" with no peripheral wired up. The block + * sits just above the console ring in the M7's DDR window, clear of the + * RPMsg reservations at 0x88000000. + */ + +#include +#include "wolfboot/wolfboot.h" + +#if defined(WOLFCRYPT_TEST) || defined(WOLFCRYPT_BENCHMARK) +#include +#include +#endif +#ifdef WOLFCRYPT_TEST +int wolfcrypt_test(void *args); +#endif +#ifdef WOLFCRYPT_BENCHMARK +int benchmark_test(void *args); +#endif + +#define APP_STATUS_ADDR 0x80F10010UL +#define APP_STATUS_MAGIC 0x41505031UL /* "APP1" */ + +extern int uart_init(uint32_t bitrate, uint8_t data, char parity, uint8_t stop); +extern void uart_write(const char *buf, unsigned int len); + +/* Local, so the banner works in builds that do not link ../src/string.o. */ +static void say(const char *s) +{ + unsigned int n = 0; + + while (s[n] != '\0') + n++; + uart_write(s, n); +} + +void main(void) +{ + volatile uint32_t *status = (volatile uint32_t *)APP_STATUS_ADDR; + uint32_t heartbeat = 0; + /* remoteproc loads only PT_LOAD segments, and .bss is NOBITS - nothing + * zeroes it before entry, so do it here before any static is read. */ + extern char _start_bss[], _end_bss[]; + char *p; + + for (p = _start_bss; p < _end_bss; p++) + *p = 0; + + (void)uart_init(115200, 8, 'N', 1); + + status[0] = APP_STATUS_MAGIC; + status[1] = heartbeat; + + say("\r\nwolfBoot test app: i.MX95 Cortex-M7\r\n"); + +#if defined(WOLFCRYPT_TEST) || defined(WOLFCRYPT_BENCHMARK) + wolfCrypt_Init(); +#ifdef WOLFCRYPT_TEST + say("\r\nRunning wolfCrypt tests...\r\n"); + wolfcrypt_test(NULL); + say("Tests complete.\r\n"); +#endif +#ifdef WOLFCRYPT_BENCHMARK + say("\r\nRunning wolfCrypt benchmarks...\r\n"); + benchmark_test(NULL); + say("Benchmarks complete.\r\n"); +#endif + wolfCrypt_Cleanup(); +#endif + + say("Idle.\r\n"); + + /* Confirm this image booted so wolfBoot does not roll it back. */ + wolfBoot_success(); + + while (1) { + status[1] = ++heartbeat; + } +} diff --git a/test-app/wolfcrypt_support.c b/test-app/wolfcrypt_support.c index 5a3e52da..a01b7adb 100644 --- a/test-app/wolfcrypt_support.c +++ b/test-app/wolfcrypt_support.c @@ -44,6 +44,47 @@ extern volatile uint64_t HAL_time_ms; #elif defined(TARGET_lpc55s69) extern volatile uint64_t SysTick_time_ms; +#elif defined(TARGET_imx95_m7) + /* Cortex-M7 DWT cycle counter. Exact, free to read, and needs no + * peripheral - which matters here because the M7 has no console UART + * routed on this carrier. The core runs at 800 MHz (read from the board's + * clk_summary: "m7 800000000"), not assumed. CYCCNT is 32-bit and wraps + * every ~5.4 s at that rate, so accumulate into 64 bits on each read; + * benchmark intervals are shorter than one wrap, so sampling on every call + * is sufficient to catch it. */ + #define IMX95_M7_HZ 800000000ULL + static uint64_t m7_cyc_hi = 0; + static uint32_t m7_cyc_last = 0; + static int m7_cyc_inited = 0; + + static void m7_cyc_init(void); + + static uint64_t m7_get_ticks(void) + { + uint32_t now; + + if (!m7_cyc_inited) { + m7_cyc_init(); + m7_cyc_inited = 1; + } + now = *(volatile uint32_t *)0xE0001004UL; /* DWT_CYCCNT */ + + if (now < m7_cyc_last) + m7_cyc_hi += 0x100000000ULL; /* wrapped since last read */ + m7_cyc_last = now; + return m7_cyc_hi + now; + } + + static void m7_cyc_init(void) + { + *(volatile uint32_t *)0xE000EDFCUL |= (1UL << 24); /* DEMCR.TRCENA */ + *(volatile uint32_t *)0xE0001004UL = 0; /* DWT_CYCCNT = 0 */ + *(volatile uint32_t *)0xE0001000UL |= 1UL; /* DWT_CTRL.CYCCNTENA */ + m7_cyc_hi = 0; + m7_cyc_last = 0; + } + + static uint64_t m7_start_ticks = 0; #elif defined(TARGET_nxp_t2080) || defined(TARGET_nxp_t1024) /* PPC time base register for accurate timing (e6500). */ static uint32_t ppc_tb_hz = 0; @@ -147,6 +188,12 @@ unsigned long my_time(unsigned long* timer) unsigned long t = (unsigned long)(SysTick_time_ms / 1000); if (timer) *timer = t; return t; +#elif defined(TARGET_imx95_m7) + { + unsigned long t = (unsigned long)(m7_get_ticks() / IMX95_M7_HZ); + if (timer) *timer = t; + return t; + } #elif defined(TARGET_nxp_t2080) || defined(TARGET_nxp_t1024) if (ppc_tb_hz == 0) ppc_tb_hz = ppc_get_timebase_hz(); @@ -191,6 +238,13 @@ double current_time(int reset) #elif defined(TARGET_lpc55s69) (void)reset; return (double)SysTick_time_ms / 1000.0; +#elif defined(TARGET_imx95_m7) + if (reset) { + m7_cyc_init(); + m7_cyc_inited = 1; + m7_start_ticks = 0; + } + return (double)(m7_get_ticks() - m7_start_ticks) / (double)IMX95_M7_HZ; #elif defined(TARGET_nxp_t2080) || defined(TARGET_nxp_t1024) if (ppc_tb_hz == 0) ppc_tb_hz = ppc_get_timebase_hz();