Add NXP i.MX95 post-quantum demo for the A55 cluster and Cortex-M7 - #13
Draft
dgarske wants to merge 1 commit into
Draft
Add NXP i.MX95 post-quantum demo for the A55 cluster and Cortex-M7#13dgarske wants to merge 1 commit into
dgarske wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds an i.MX95 “post-quantum on both clusters” demo that pairs Linux-side wolfCrypt PQC benchmarks (A55) with wolfBoot ML-DSA-87 verified boot and console relay from the Cortex-M7.
Changes:
- Add a
/dev/memmmap-basedmemtoolutility for staging payloads and dumping the M7 shared-memory console. - Add a Zephyr OpenAMP/RPMsg payload for M7 that relays wolfBoot’s shared-memory console to Linux as an RPMsg TTY.
- Add board orchestration scripts, a two-pane console renderer, and a benchmark container build/runtime setup.
Reviewed changes
Copilot reviewed 18 out of 20 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| imx95-pqc-demo/tools/memtool.c | Adds /dev/mem mmap read/write/load/fill and M7 console ring dump tooling. |
| imx95-pqc-demo/m7/zephyr-app/src/main.c | Implements the M7-side OpenAMP/RPMsg endpoint and wolfBoot console relay logic. |
| imx95-pqc-demo/m7/zephyr-app/prj.conf | Configures Zephyr for OpenAMP + minimal logging and documents MPU constraints. |
| imx95-pqc-demo/m7/zephyr-app/CMakeLists.txt | Defines the Zephyr application build. |
| imx95-pqc-demo/m7/imx95_wolfboot.overlay | Relocates the Zephyr image, enables MU, and declares MPU-accessible console/status region. |
| imx95-pqc-demo/m7/build.sh | Provides a reproducible Zephyr build producing a raw binary payload. |
| imx95-pqc-demo/demo/twopane.py | Renders the two-pane demo view on a console without tmux/screen dependencies. |
| imx95-pqc-demo/demo/stage.sh | Stages demo artifacts to the board and builds/stages memtool. |
| imx95-pqc-demo/demo/m7-start.sh | Starts remoteproc and stages the signed payload into the M7 boot partition region. |
| imx95-pqc-demo/demo/m7-console-tail.sh | Provides a console-ring tailer for the M7 pane (alternative to RPMsg TTY). |
| imx95-pqc-demo/demo/demo-run.sh | Orchestrates container start, M7 start, and launches the renderer to a target TTY. |
| imx95-pqc-demo/container/entrypoint.sh | Runs benchmark cycles and selects baseline vs sha3-crypto build at runtime. |
| imx95-pqc-demo/container/docker-compose.yml | Defines the benchmark container service for on-board execution/logging. |
| imx95-pqc-demo/container/build-image.sh | Builds (and optionally pushes) the arm64 demo container image. |
| imx95-pqc-demo/container/build-aarch64.sh | Cross-builds wolfSSL for aarch64 in baseline and sha3-crypto configs. |
| imx95-pqc-demo/container/Dockerfile | Multi-stage image build that cross-compiles wolfSSL and ships benchmarks. |
| imx95-pqc-demo/README.md | Documents the demo purpose, measurements, build steps, and key gotchas. |
| README.md | Adds the new i.MX95 PQC demo to the repository’s example list. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| unsigned n = (argc > 3) ? (unsigned)strtoul(argv[3], NULL, 0) : 8; | ||
| unsigned i; | ||
|
|
||
| maplen = map_region(addr, n * 4, &base, &ptr, &fd); |
Comment on lines
+90
to
+92
| len = (size_t)strtoul(argv[3], NULL, 0); | ||
| byte = (int)strtoul(argv[4], NULL, 16); | ||
| maplen = map_region(addr, len, &base, &ptr, &fd); |
Comment on lines
+97
to
+108
| * /dev/mem mapping on this part. Fill in aligned 32-bit words. */ | ||
| { | ||
| volatile uint32_t *dst = (volatile uint32_t *)ptr; | ||
| uint32_t word; | ||
| size_t words, i; | ||
|
|
||
| word = (uint32_t)(byte & 0xff); | ||
| word |= (word << 8); | ||
| word |= (word << 16); | ||
| words = (len + 3) / 4; | ||
| for (i = 0; i < words; i++) | ||
| dst[i] = word; |
| fprintf(stderr, "open %s: %s\n", argv[3], strerror(errno)); | ||
| return 1; | ||
| } | ||
| maplen = map_region(addr, (size_t)st.st_size, &base, &ptr, &fd); |
Comment on lines
+150
to
+152
| words = ((size_t)st.st_size + 3) / 4; | ||
| for (i = 0; i < words; i++) | ||
| dst[i] = src[i]; |
Comment on lines
+148
to
+152
| got = fread(buf, 1, (size_t)st.st_size, f); | ||
| src = (const uint32_t *)buf; | ||
| words = ((size_t)st.st_size + 3) / 4; | ||
| for (i = 0; i < words; i++) | ||
| dst[i] = src[i]; |
Comment on lines
+312
to
+318
| size = h->size; | ||
| wr = h->wr; | ||
| if (size == 0U || wr == *pos) { | ||
| return; | ||
| } | ||
|
|
||
| avail = wr - *pos; |
Comment on lines
+191
to
+193
| ARG_UNUSED(priv); | ||
| ipm_send(ipm_handle, 0, id, &id, sizeof(id)); | ||
| return 0; |
Comment on lines
+61
to
+67
| def run(cmd): | ||
| try: | ||
| p = subprocess.run(cmd, shell=True, capture_output=True, | ||
| text=True, timeout=5) | ||
| return p.stdout | ||
| except Exception: | ||
| return "" |
Comment on lines
+100
to
+101
| left = fit(clean(run(f"docker logs --tail 400 {CONTAINER} 2>&1"), drop_cycles=True), half, body) | ||
| right_raw = run(f"{MEMTOOL} con {CONSOLE_ADDR} 2>/dev/null") |
dgarske
force-pushed
the
imx95_pqc_demo
branch
2 times, most recently
from
August 21, 2026 03:17
f536603 to
486acd5
Compare
dgarske
force-pushed
the
imx95_pqc_demo
branch
from
August 21, 2026 03:56
486acd5 to
68e60a7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on wolfBoot PR #863 (
imx95_m7target). The demo builds wolfBoot for that target; merge it first.What this adds
A two-pane demo running entirely on a Toradex SMARC iMX95, on one screen:
The pairing is the point: the same post-quantum algorithms at the same security level, on a Linux application cluster and a bare-metal real-time core in one SoC.
Measured on hardware
Cortex-M7 at 800 MHz, DWT cycle counter, caches enabled:
Post-quantum verified boot costs 5.25 ms, and the ML-DSA-87 bootloader is 940 bytes smaller than the ECDSA one - ML-DSA verification is SHAKE plus polynomial arithmetic and never pulls in the big-integer math P-256 needs. Verification also scales well: 1,004 B to 54,080 B is 54x the payload for 1.41x the verify.
Three things the README documents because they cost real time
The payload is deliberately not
samples/subsys/ipc/openamp_rsc_table. That sample assumes Linux loaded its ELF: the remote declares vrings withda = FW_RSC_ADDR_ANYand Linux writes the resolved addresses and virtio status back into the table it loaded. Here Linux loads wolfBoot, so those land in wolfBoot's table while the sample polls its own - and it hangs forever inrproc_virtio_wait_remote_ready(). This payload uses fixed vring addresses matching wolfBoot's table and skips the wait, because Linux registers virtio0 before releasing the core.The M7 starts once per Linux boot.
echo stop > .../statefails on this BSP and the core stays running, so replaying the boot is a real board power cycle, not a restart.Torizon ships no tmux and its rootfs is read-only OSTree, so there is nothing to install.
demo/twopane.pyrenders two fixed columns with a full redraw instead. The HDMI output is a real text console (getty@tty1), so it renders there directly.Note on the benchmark output
The renderer strips the benchmark's
Cycles/opandCycles per bytecolumns. On this part they derive from the 24 MHz generic timer rather than the 1.8 GHz core clock and are wrong by roughly 75x - they should not appear on a demo screen. ops/sec and MB/s are correct and are what the pane shows.Testing
Built and run on a Toradex SMARC iMX95 Hexa 8GB, Torizon OS 7.7.0, M7 at 800 MHz. The container was verified on the board (
docker compose up), and containerised benchmark numbers match the native ones (ML-KEM-768 encap 17,074 vs 17,112 native; ML-DSA-87 verify 1,720 vs 1,711). The Zephyr payload was rebuilt from the packaged copy to confirm the directory is self-contained: 54,240 bytes, linked at0x80103000.No board-specific credentials, addresses or vendor boot binaries are included.