diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 2342589dc5..6e226dbc37 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -385,6 +385,15 @@ jobs: config-file: ./config/examples/raspi3-encrypted.config make-args: wolfboot.bin CROSS_COMPILE=aarch64-linux-gnu- + imx95_m7_test: + uses: ./.github/workflows/test-build.yml + with: + arch: arm + # i.MX95 Cortex-M7: wolfBoot runs from ITCM (loaded by remoteproc) and + # the payload lives in DDR, so there is no contiguous factory.bin. + config-file: ./config/examples/imx95-m7.config + make-args: wolfboot.bin test-app/image_v1_signed.bin + tegra234_test: uses: ./.github/workflows/test-build.yml with: diff --git a/Makefile b/Makefile index 158e8655e1..f15a4522f7 100644 --- a/Makefile +++ b/Makefile @@ -376,6 +376,12 @@ ifeq ($(TARGET),tegra234) MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin endif +# i.MX95 M7 runs from ITCM (loaded by the Linux remoteproc driver); the payload +# lives in DDR at 0x80100000, so there is no contiguous flash image to assemble. +ifeq ($(TARGET),imx95_m7) + MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin +endif + ifeq ($(TARGET),sim) CFLAGS+=-fno-pie LDFLAGS+=-no-pie diff --git a/arch.mk b/arch.mk index 867e9ac4f6..d37e5732d7 100644 --- a/arch.mk +++ b/arch.mk @@ -259,6 +259,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 0000000000..cc7232b727 --- /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/docs/Targets.md b/docs/Targets.md index e685766ced..d832f2a621 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -19,6 +19,7 @@ This README describes configuration of supported targets. * [Nordic nRF52840](#nordic-nrf52840) * [Nordic nRF5340](#nordic-nrf5340) * [Nordic nRF54L15](#nordic-nrf54l15) +* [NXP i.MX95 Cortex-M7](#nxp-imx95-cortex-m7) * [NXP iMX-RT](#nxp-imx-rt) * [NXP Kinetis](#nxp-kinetis) * [NXP Kinetis KL26Z](#nxp-kinetis-kl26z) @@ -8958,3 +8959,84 @@ Boot success marked. Version: 1 | `FLAGS_HOME` | Keep boot flags in internal flash (required when `EXT_FLASH=1`). | | `MAX3266X_TPU` | Enable TPU hardware SHA256 acceleration (requires `MSDK_DIR`). | | `MAX3266X_OLD` | Build TPU acceleration against the older, deprecated Maxim SDK tree instead of the modern MSDK. | + + +## NXP i.MX95 Cortex-M7 + +The i.MX95 pairs an A55 cluster running Linux with a real-time Cortex-M7 and a Cortex-M33 System Manager. The M7 has no dedicated flash: it is loaded into TCM by the Linux `remoteproc` driver on the A55 side, so wolfBoot is the ELF that `remoteproc` loads, and the images it verifies live in the DDR region the device tree reserves for the M7. + +Validated on a Toradex SMARC i.MX95 module with `TARGET=imx95_m7`. + +### i.MX95: Memory layout + +wolfBoot is linked for the *core* view of TCM. `remoteproc` loads through the *system* view and `imx_rproc` translates between the two; linking for the system view produces an image that loads cleanly and faults on the first instruction fetch. + +| Region | Core view | System view | Size | +|--------|-----------|-------------|------| +| ITCM (wolfBoot text) | `0x00000000` | `0x203C0000` | 256 KiB | +| DTCM (wolfBoot data, app data/heap/stack) | `0x20000000` | `0x20400000` | 256 KiB | +| Reserved DDR (`memory@80000000`) | `0x80000000` | - | 16 MiB | + +The TCM split assumes `M7_CFG[TCM_SIZE] = 000b` (256 KiB + 256 KiB), the reset default. That field lives in `BLK_CTRL_Secure_AON` and is owned by the System Manager running on the M33, not by wolfBoot. + +The "flash" wolfBoot writes is ordinary DDR: writes are copies and erases are fills. Linux stages an update by writing the UPDATE partition before restarting the core. + +| Block | Address | Size | +|-------|---------|------| +| BOOT partition | `0x80100000` | 4 MiB | +| UPDATE partition | `0x80500000` | 4 MiB | +| SWAP | `0x80900000` | 16 KiB sector | +| Console header + ring | `0x80F00000` | 64 KiB | +| wolfBoot status block | `0x80F10000` | 4 words | +| Test-app status block | `0x80F10010` | 2 words | + +The RPMsg carveouts at `0x88000000` (vrings) and `0x88020000` (vdevbuffer) belong to the RPMsg transport and are deliberately not used for the console or status blocks. wolfBoot still carries the `remoteproc` resource table declaring them, because Linux looks for `.resource_table` in the ELF *it* loads - without it the kernel logs `No resource table in elf` and never creates the virtio device. + +### i.MX95: Building + +```sh +cp config/examples/imx95-m7.config .config +make +``` + +This produces `wolfboot.elf` (the image `remoteproc` loads) and `test-app/image_v1_signed.bin` (the payload Linux writes to `0x80100000`). There is no `factory.bin`: wolfBoot runs from ITCM and the payload lives in DDR, so there is no contiguous flash image to assemble. + +To build the test app with the wolfCrypt test suite and benchmark: + +```sh +make WOLFCRYPT_TEST=1 WOLFCRYPT_BENCHMARK=1 +``` + +Benchmark timing comes from the Cortex-M7 DWT cycle counter, scaled by `IMX95_M7_HZ` (800 MHz by default, matching this board's `clk_summary` entry `m7 800000000`). The M7 cannot read its own clock rate without an SCMI round trip to the System Manager, so if that rate is configured differently the constant must be overridden or every reported figure scales by the ratio: + +```sh +make WOLFCRYPT_BENCHMARK=1 CFLAGS_EXTRA=-DIMX95_M7_HZ=1000000000ULL +``` + +### i.MX95: Console and status + +The M7's LPUART is not routed to a host-accessible header on this carrier, and Linux owns all four LPUART instances, so the console is a ring buffer in the M7's own DDR window rather than a peripheral driver. Its header is: + +| Offset | Field | Meaning | +|--------|-------|---------| +| `0x00` | `magic` | `0x4E4F4357` - the stored bytes read `WCON` in a hexdump | +| `0x04` | `wr` | Total bytes ever written, monotonic within a run | +| `0x08` | `size` | Ring size in bytes; read it rather than assuming | +| `0x0C` | `rsvd` | Reserved | + +A reader tracks its own position `pos` and copies `wr - pos` bytes starting at `data[pos % size]`. Overrun is possible for very chatty output and is detected when `wr - pos > size`, so the reader reports the gap instead of printing corrupt text. + +Progress is also published as plain 32-bit words that Linux can poll with `devmem` without parsing console text: + +```sh +devmem 0x80F10000 # 0x57424F54 "WBOT" - wolfBoot status block valid +devmem 0x80F10004 # progress code: 1 = hal_init, 2 = hal_prepare_boot +devmem 0x80F10008 # DWT cycle count at hal_init +devmem 0x80F1000C # DWT cycle count at hal_prepare_boot +devmem 0x80F10010 # 0x41505031 "APP1" - the payload is running +devmem 0x80F10014 # heartbeat, incrementing +``` + +The difference between the two timestamps is the cost of everything wolfBoot does in between, which is dominated by signature verification. Note that these magics are spelled to read correctly as `devmem` 32-bit words, the opposite convention from the console magic, which is read from a hexdump of the ring. + +Both caches are enabled by `hal_init()`, which matters because verifying an image means hashing megabytes resident in DDR. The ARMv7-M default memory map marks `0x80000000-0x9FFFFFFF` as Normal write-through, so no MPU region is needed and M7 stores to the shared window still reach DDR; the HAL nevertheless cleans the affected lines explicitly so that behaviour is not left depending on an inherited attribute. diff --git a/hal/imx95_m7.c b/hal/imx95_m7.c new file mode 100644 index 0000000000..759d16e449 --- /dev/null +++ b/hal/imx95_m7.c @@ -0,0 +1,323 @@ +/* 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" +#include "hal/imx95_m7.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 + +/* The M7 has no confirmed UART route on this carrier, so early bring-up + * reports progress through the shared status block that Linux can read with + * devmem instead of over a serial port. Its address, and the console ring + * immediately below it, live in hal/imx95_m7.h. + * + * Both caches are OFF out of reset. wolfBoot's own text runs from ITCM (see + * hal/imx95_m7.ld), so the I-cache buys it little; the D-cache is what matters, + * because verifying an image means hashing multiple megabytes that live in + * DDR - measured at ~664 cycles per byte for SHA-256 with the cache off, + * roughly fifty times slower than the core is capable of. + * + * The ARMv7-M default memory map already marks 0x80000000-0x9FFFFFFF as normal + * write-through cacheable memory, so no MPU region is needed to make this take + * effect, and stores to the shared window still reach DDR. */ + +/* Fill sets/ways/shift from CCSIDR for the L1 data cache. The shifts are + * geometry-dependent (ARMv7-M DDI 0403: way field starts at 32-log2(assoc), + * set field at log2(line size)), so derive both rather than assuming the + * Cortex-M7's fixed 4-way, 32-byte-line configuration. */ +static void hal_dcache_geometry(uint32_t *sets, uint32_t *ways, + uint32_t *way_shift, uint32_t *set_shift) +{ + uint32_t ccsidr, tmp, shift; + + SCB_CSSELR = 0UL; /* select L1 data cache */ + DSB(); + ccsidr = SCB_CCSIDR; + + *sets = ((ccsidr >> 13) & 0x7FFFUL) + 1UL; + *ways = ((ccsidr >> 3) & 0x3FFUL) + 1UL; + *set_shift = (ccsidr & 0x7UL) + 4UL; + + shift = 32UL; + tmp = *ways - 1UL; + while (tmp != 0UL) { + shift--; + tmp >>= 1; + } + *way_shift = shift; +} + +static void hal_cache_enable(void) +{ + uint32_t sets, ways, way_shift, set_shift, 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. */ + hal_dcache_geometry(&sets, &ways, &way_shift, &set_shift); + for (s = 0; s < sets; s++) { + for (w = 0; w < ways; w++) { + SCB_DCISW = ((w << way_shift) | (s << set_shift)); + } + } + DSB(); + SCB_CCR |= CCR_DC; + DSB(); ISB(); +} + +/* Clean every dirty line back to DDR and drop the I-cache. Used before handing + * off to an image wolfBoot may have just written through the D-cache: the + * Cortex-M7 instruction side does not snoop the data side, so without this the + * jump would rely on the default map happening to be write-through. */ +static void hal_cache_flush(void) +{ + uint32_t sets, ways, way_shift, set_shift, s, w; + + hal_dcache_geometry(&sets, &ways, &way_shift, &set_shift); + for (s = 0; s < sets; s++) { + for (w = 0; w < ways; w++) { + SCB_DCCSW = ((w << way_shift) | (s << set_shift)); + } + } + DSB(); + SCB_ICIALLU = 0UL; + DSB(); ISB(); +} + +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; +} + +/* hal_init()/hal_prepare_boot() are deliberately NOT wrapped in + * #ifdef __WOLFBOOT: test-app/Makefile pulls ../hal/imx95_m7.o into APP_OBJS + * and rebuilds it in place with the application's flags, so an object that + * only defines them for the bootloader build would be clobbered and break the + * next wolfboot.elf link. The application simply never calls them, and + * --gc-sections drops them from its image. + * + * Progress codes stamped into the shared status block. Kept deliberately + * trivial: no locking, no ordering guarantees beyond the store itself. */ +#define HAL_STATUS_INIT 1U +#define HAL_STATUS_PREBOOT 2U + +/* Record a progress code where the A55 side can see it. The block is only + * WOLFBOOT_STATUS_WORDS words wide and the word above it is the test app's + * liveness magic, so the timestamp slot is selected explicitly rather than + * indexed by the caller's code. */ +static 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. */ + switch (code) { + case HAL_STATUS_INIT: + status[2] = DWT_CYCCNT; + break; + case HAL_STATUS_PREBOOT: + status[3] = DWT_CYCCNT; + break; + default: + break; + } + imx95_dcache_clean((const void *)status, + WOLFBOOT_STATUS_WORDS * sizeof(uint32_t)); +} + +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 the partitions it hashes + * live in DDR. + * + * .bss and .data are handled by src/boot_arm.c:isr_reset(): remoteproc + * loads only PT_LOAD segments and .bss is NOBITS, so nothing external + * zeroes it before entry. */ + hal_cache_enable(); + imx95_dwt_init(); + hal_status(HAL_STATUS_INIT); +} + +void hal_prepare_boot(void) +{ + hal_status(HAL_STATUS_PREBOOT); + /* wolfBoot may have just written the image being booted into DDR through + * the D-cache; make that visible to the instruction side before jumping. */ + hal_cache_flush(); +} + +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.h b/hal/imx95_m7.h new file mode 100644 index 0000000000..3744f3a187 --- /dev/null +++ b/hal/imx95_m7.h @@ -0,0 +1,172 @@ +/* imx95_m7.h + * + * Shared definitions 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 shared-memory layout below is read by the A55 cluster running Linux, so + * it is described in exactly one place: the HAL, the console driver and the + * test app all include this header rather than repeating the addresses. */ + +#ifndef IMX95_M7_H +#define IMX95_M7_H + +#include + +/* --------------------------------------------------------------------------- + * Memory map + * + * The M7's reserved DDR window is memory@80000000, 16 MiB. wolfBoot's + * partitions sit in the lower part of it (BOOT 0x80100000, UPDATE 0x80500000, + * SWAP 0x80900000); the shared window below occupies the top of it, clear of + * the RPMsg reservations at 0x88000000 which belong to the transport. + * + * 0x80F00000 console header (16 B) + ring (65520 B) + * 0x80F10000 wolfBoot status block (4 words) + * 0x80F10010 test-app status block (2 words) + * ------------------------------------------------------------------------- */ + +#define M7_DDR_BASE 0x80000000UL +#define M7_DDR_SIZE 0x01000000UL + +/* Console ring. CONSOLE_REGION is the whole 64 KiB page; the ring is what is + * left of it once the header is accounted for, so the console cannot run into + * the status block above it. Cortex-M7 has a hardware UDIV, so the + * non-power-of-two modulo in uart_tx() costs a few cycles, not a helper call. */ +#define CONSOLE_BASE 0x80F00000UL +#define CONSOLE_HDR_SIZE 16UL +#define CONSOLE_REGION 0x00010000UL +#define CONSOLE_SIZE (CONSOLE_REGION - CONSOLE_HDR_SIZE) +/* Stored bytes are 57 43 4F 4E, so a hexdump of the ring reads "WCON". */ +#define CONSOLE_MAGIC 0x4E4F4357UL + +/* Status blocks. These are read one 32-bit word at a time with devmem, which + * prints the word rather than its bytes, so the magics are spelled to read + * correctly in that view ("WBOT", "APP1") - the opposite convention from + * CONSOLE_MAGIC above, which is read from a hexdump. */ +#define WOLFBOOT_STATUS_ADDR 0x80F10000UL +#define WOLFBOOT_STATUS_WORDS 4U +#define WOLFBOOT_STATUS_MAGIC 0x57424F54UL /* "WBOT" */ + +#define APP_STATUS_ADDR (WOLFBOOT_STATUS_ADDR + (WOLFBOOT_STATUS_WORDS * 4UL)) +#define APP_STATUS_WORDS 2U +#define APP_STATUS_MAGIC 0x41505031UL /* "APP1" */ + +/* The ring and the status block used to overlap by exactly the header size. + * Fail the build rather than let the two drift apart again. */ +typedef char imx95_console_fits_below_status[ + ((CONSOLE_BASE + CONSOLE_HDR_SIZE + CONSOLE_SIZE) <= WOLFBOOT_STATUS_ADDR) + ? 1 : -1]; + +/* --------------------------------------------------------------------------- + * Barriers and cache maintenance + * + * Both L1 caches are off out of reset. Once they are on, note that "volatile" + * only binds the compiler: it places no barrier on the core, and the A55 is a + * genuinely separate observer of the shared window, so publish protocols need + * a real DMB. + * + * The ARMv7-M default memory map makes 0x80000000-0x9FFFFFFF Normal + * write-through, which is why M7 stores to the shared window reach DDR at all + * without maintenance. The explicit cleans below cost close to nothing on a + * write-through line and stop that inherited attribute from being load-bearing + * if a future MPU region, NO_MPU=0 or a different carveout base changes it. + * ------------------------------------------------------------------------- */ + +#define DMB() __asm__ volatile ("dmb 0xF" ::: "memory") +#define DSB() __asm__ volatile ("dsb 0xF" ::: "memory") +#define ISB() __asm__ volatile ("isb 0xF" ::: "memory") + +#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 SCB_DCCMVAC (*(volatile uint32_t *)0xE000EF68UL) +#define SCB_DCCSW (*(volatile uint32_t *)0xE000EF6CUL) +#define CCR_IC (1UL << 17) +#define CCR_DC (1UL << 16) + +/* Cortex-M7 L1 line size. Also derived from CCSIDR where the set/way loops + * need it; this constant is only for maintenance by address. */ +#define IMX95_CACHE_LINE 32UL + +static inline void imx95_dcache_clean(const void *addr, uint32_t len) +{ + uint32_t line; + uint32_t end; + + if (len == 0) + return; + line = ((uint32_t)(uintptr_t)addr) & ~(IMX95_CACHE_LINE - 1UL); + end = (uint32_t)(uintptr_t)addr + len; + for (; line < end; line += IMX95_CACHE_LINE) { + SCB_DCCMVAC = line; + } + DSB(); +} + +/* --------------------------------------------------------------------------- + * DWT cycle counter + * + * With no console UART routed on this carrier the cycle counter is how boot + * cost gets measured: exact, free to read, and needing no peripheral. + * ------------------------------------------------------------------------- */ + +#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) + +/* Idempotent: a second caller must not restart a counter someone else is + * already extending to 64 bits. */ +static inline void imx95_dwt_init(void) +{ + if ((DWT_CTRL & DWT_CYCCNTENA) != 0UL) + return; + CORE_DEMCR |= DEMCR_TRCENA; + DWT_CYCCNT = 0; + DWT_CTRL |= DWT_CYCCNTENA; +} + +/* Bounded spin. Wrap-safe for any delay shorter than a full 32-bit period + * (~5.4 s at 800 MHz) because the comparison is done on the difference. */ +static inline void imx95_delay_cycles(uint32_t cycles) +{ + uint32_t start = DWT_CYCCNT; + + while ((uint32_t)(DWT_CYCCNT - start) < cycles) + ; +} + +/* --------------------------------------------------------------------------- + * SysTick (test app only; wolfBoot itself runs with interrupts off) + * ------------------------------------------------------------------------- */ + +#define SYST_CSR (*(volatile uint32_t *)0xE000E010UL) +#define SYST_RVR (*(volatile uint32_t *)0xE000E014UL) +#define SYST_CVR (*(volatile uint32_t *)0xE000E018UL) +#define SYST_CSR_ENABLE (1UL << 0) +#define SYST_CSR_TICKINT (1UL << 1) +#define SYST_CSR_CLKSOURCE (1UL << 2) +#define SYST_RVR_MAX 0x00FFFFFFUL + +#endif /* IMX95_M7_H */ diff --git a/hal/imx95_m7.ld b/hal/imx95_m7.ld new file mode 100644 index 0000000000..b98b68026c --- /dev/null +++ b/hal/imx95_m7.ld @@ -0,0 +1,89 @@ +/* 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. + * + * Align the output SECTION, not the location counter inside it. Linux's + * rproc_elf_find_loaded_rsc_table() finds the table by the section + * header's sh_addr, so an inner ". = ALIGN(8)" that has to pad puts those + * pad bytes *inside* the section: the kernel then reads the padding as + * fw_rsc_hdr::ver, sees 0, rejects the table and never creates the vdev. + * This form keeps sh_addr and the resource_table symbol coincident + * whatever precedes the section. */ + .resource_table ALIGN(8) : + { + KEEP(*(.resource_table*)) + } > ITCM + + /* Public keys emitted by the keytool (KEYSTORE_SECTION). Placed + * explicitly so it cannot land after _stored_data as an orphan. */ + .keystore : + { + . = ALIGN(8); + KEEP(*(.keystore*)) + . = ALIGN(8); + } > ITCM + + .edidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > ITCM + + _stored_data = .; + + .data : AT (_stored_data) + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = 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 0000000000..0524582af6 --- /dev/null +++ b/hal/uart/uart_drv_imx95_m7.c @@ -0,0 +1,117 @@ +/* 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. The header plus + * the ring occupy exactly the 64 KiB page below the status block; see + * hal/imx95_m7.h, which holds the layout and asserts that they do not overlap. + * + * 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]. `size` is published in the header rather than assumed. + * 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. + * + * Publish ordering is enforced with DMB, not by `volatile`: volatile binds the + * compiler only, while the A55 reading this ring is a separate observer of a + * Normal-memory region the M7 may reorder stores to. + */ + +#include +#include "hal/imx95_m7.h" + +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 + CONSOLE_HDR_SIZE)) + +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, and behind a barrier: a reader that samples mid-init sees no + * valid console rather than a valid magic with a stale size. */ + DMB(); + h->magic = CONSOLE_MAGIC; + imx95_dcache_clean((const void *)h, sizeof(*h)); + return 0; +} + +int uart_tx(const uint8_t c) +{ + struct console_hdr *h = CONSOLE_HDR; + uint32_t w; + uint32_t idx; + + if (h->magic != CONSOLE_MAGIC) + (void)uart_init(0, 0, 'N', 0); + + w = h->wr; + idx = w % CONSOLE_SIZE; + CONSOLE_DATA[idx] = c; + imx95_dcache_clean((const void *)&CONSOLE_DATA[idx], 1); + /* Publish the byte before advancing the counter, so a reader never sees a + * count covering a byte that has not been stored yet. */ + DMB(); + h->wr = w + 1; + imx95_dcache_clean((const void *)h, sizeof(*h)); + 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 0000000000..28abd89f3c --- /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 7312762376..8e4affec3f 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -616,6 +616,19 @@ 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.. + # The app owns isr_systick: it samples the DWT cycle counter often enough + # that the 32-bit counter cannot wrap unnoticed between timebase reads. + CFLAGS+=-DAPP_HAS_SYSTICK + 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 0000000000..1551eb52d2 --- /dev/null +++ b/test-app/app_imx95_m7.c @@ -0,0 +1,144 @@ +/* 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. The addresses come from hal/imx95_m7.h, + * which is also where the two blocks are asserted not to overlap. + */ + +#include +#include "wolfboot/wolfboot.h" +#include "hal/imx95_m7.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 + +/* A heartbeat visible to a devmem poller does not need to run at core speed; + * an unthrottled loop just burns DDR bandwidth the A55 cluster shares. */ +#define APP_HEARTBEAT_CYCLES 0x04000000UL /* ~84 ms at 800 MHz */ + +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); + +#if defined(WOLFCRYPT_TEST) || defined(WOLFCRYPT_BENCHMARK) +/* Defined in test-app/wolfcrypt_support.c: folds the 32-bit DWT counter into + * the 64-bit timebase. Called often enough here that a wrap cannot be missed + * between two timebase reads, however far apart those are. */ +extern void imx95_m7_cyc_sample(void); + +static void systick_start(void) +{ + /* Full 24-bit reload: ~21 ms at 800 MHz, far inside the ~5.4 s CYCCNT + * wrap, and it needs no knowledge of the actual core clock. */ + SYST_RVR = SYST_RVR_MAX; + SYST_CVR = 0; + SYST_CSR = SYST_CSR_CLKSOURCE | SYST_CSR_TICKINT | SYST_CSR_ENABLE; +} +#endif + +/* The vector slot is claimed unconditionally (APP_HAS_SYSTICK in + * test-app/Makefile), so the handler must exist even in builds with no + * timebase to sample; SysTick is only started when there is one. */ +void isr_systick(void) +{ +#if defined(WOLFCRYPT_TEST) || defined(WOLFCRYPT_BENCHMARK) + imx95_m7_cyc_sample(); +#endif +} + +/* 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; + + /* .data and .bss are already handled by test-app/startup_arm.c:isr_reset(), + * which runs from this image's own vector table before main(). */ + + (void)uart_init(115200, 8, 'N', 1); + + status[0] = APP_STATUS_MAGIC; + status[1] = heartbeat; + imx95_dcache_clean((const void *)status, + APP_STATUS_WORDS * sizeof(uint32_t)); + + say("\r\nwolfBoot test app: i.MX95 Cortex-M7\r\n"); + +#if defined(WOLFCRYPT_TEST) || defined(WOLFCRYPT_BENCHMARK) + systick_start(); + 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(); + + imx95_dwt_init(); + while (1) { + status[1] = ++heartbeat; + imx95_dcache_clean((const void *)&status[1], sizeof(uint32_t)); + imx95_delay_cycles(APP_HEARTBEAT_CYCLES); + } +} diff --git a/test-app/wolfcrypt_support.c b/test-app/wolfcrypt_support.c index 5a3e52daea..a9a5c6d601 100644 --- a/test-app/wolfcrypt_support.c +++ b/test-app/wolfcrypt_support.c @@ -44,6 +44,67 @@ 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. + * + * IMX95_M7_HZ is a build-time constant, not a run-time reading: the M7 + * clock is owned by the System Manager on the M33 and the core cannot + * query it without an SCMI round trip. 800 MHz is what this board's + * clk_summary reports ("m7 800000000"); override with -DIMX95_M7_HZ if the + * System Manager is configured differently, or every reported figure + * scales by the ratio. + * + * CYCCNT is 32 bits and wraps every ~5.4 s at that rate, and the wrap + * accounting below can only recover one wrap per read. my_time() alone + * does not read often enough to guarantee that - it is called only when + * wolfCrypt validates a certificate date - so app_imx95_m7.c drives + * imx95_m7_cyc_sample() from SysTick to keep the invariant true. */ + #include "hal/imx95_m7.h" + + #ifndef IMX95_M7_HZ + #define IMX95_M7_HZ 800000000ULL + #endif + + static uint64_t m7_cyc_hi = 0; + static uint32_t m7_cyc_last = 0; + static int m7_cyc_inited = 0; + static uint64_t m7_start_ticks = 0; + + /* Reached from both thread and interrupt context, so the wrap accounting + * is a critical section. PRIMASK is saved and restored rather than + * unconditionally re-enabled, since the caller may already be masked. */ + static uint64_t m7_get_ticks(void) + { + uint32_t now, primask; + uint64_t ticks; + + __asm__ volatile ("mrs %0, primask" : "=r"(primask)); + __asm__ volatile ("cpsid i" ::: "memory"); + + if (!m7_cyc_inited) { + imx95_dwt_init(); + m7_cyc_hi = 0; + m7_cyc_last = 0; + m7_cyc_inited = 1; + } + now = DWT_CYCCNT; + if (now < m7_cyc_last) + m7_cyc_hi += 0x100000000ULL; /* wrapped since last read */ + m7_cyc_last = now; + ticks = m7_cyc_hi + now; + + if ((primask & 1U) == 0U) + __asm__ volatile ("cpsie i" ::: "memory"); + return ticks; + } + + /* Called from the app's SysTick handler; see app_imx95_m7.c. */ + void imx95_m7_cyc_sample(void) + { + (void)m7_get_ticks(); + } #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 +208,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 +258,13 @@ double current_time(int reset) #elif defined(TARGET_lpc55s69) (void)reset; return (double)SysTick_time_ms / 1000.0; +#elif defined(TARGET_imx95_m7) + /* Take a new origin rather than zeroing the counter: my_time() shares it + * and the benchmark resets once per algorithm, which would otherwise walk + * wolfCrypt's notion of wall-clock time backwards dozens of times a run. */ + if (reset) + m7_start_ticks = m7_get_ticks(); + 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();