Embed M7 firmware in TorizonCore and load it automatically on Verdin iMX8M-Mini

This message from @hfranco.tx describes the necessary steps to:

  1. copy the firmware for M7 core into TorizonCore
  2. load it into M7
  3. start it

There are two possiiblities: bootaux (I think this is "from thebootloadwer u-boot) and remoteproc (from TorizonCore).

After the firmware for M7 has been built (.elf or .bin file), which is the suggested way to “automate” the previous steps in the TorizonCore image?
Do I need a custom container that does the previous steps automatically?
Is this different for bootaux or remoteproc?


Hi @vix,

In general, I recommend using the bootaux command from u-boot instead of using the remote proc approach. The difference is that bootaux u-boot will load the M7 binary before the kernel even starts, so it’s the fastest way to turn on your M7 cortex.

The remote proc approach uses the linux kernel driver called imx_rproc, which will launch the M7 cortex using the linux userspace. So the positive side here is that you can control your M7 with the userspace (reload the M7, change the binary) without rebooting, but you have to do everything manually after the kernel has started (takes more time and more commands).

Also, it has been reported some errors with this remote proc driver from NXP, so you can face some errors that are not yet fixed.

To automate the u-boot part, please check this article: How to load compiled binaries into Cortex-M | Toradex Developer Center

You can do this manually on the u-boot terminal and save the files or change the u-boot environment variables file inside the Tezi image.

Let me know if you have any questions.

Best Regards,

1 Like

Thanks @hfranco.tx
I’m going with bootaux since in the next few weeks I start working with iMX8M-Plus and (in another message) you told me that remoteproc doesn’t work yet on the Plus under BSP 6/TorizonCore 6.

I have some questions and suggestions on the article How to load compiled binaries into Cortex-M | Toradex Developer Center:

The Introduction says

this article will cover only two methods, described in the next sections.

but only EXT4LOAD Loading Method is described. Probably the info in Introduction should be fixed.

In section EXT4LOAD Loading Method I see

Follow the next steps to load the binary from the eMMC where the OS Image is, using EXT4LOAD.

and I suggest to add “from u-boot” so that everything is clearer at a first sight to a not-experienced user too (like me).

For the steps form 1 ongoing, it’s not immediately clear which one should be executed while the SoM is running Linux/TorizonCore, and which one while is running u-boot.
As far as I understand, point 1 should be executed while the SoM is running Linux, then the device should be rebooted and halted to u-boot to execute all the other steps in EXT4LOAD Loading Method section.

Can you confirm this?
Do yoy think that clarifying the documentation can help other users?


Hi @vix,

You’re correct, we had the “FATLOAD” method as well where you can load the binary from an SD card for example, and we forgot to fix the introduction. I will fix that.

Noted, I will add that too, thanks for the feedback.

Yes, you’re correct, I mentioned to you the remote proc approach but we don’t have any documents about the remote proc yet. As I said earlier, we know that the remote proc doesn’t always work and sometimes gives some kernel panics, that’s why we didn’t document this approach yet. We’re still testing it to create a guide in the future.

I really appreciate the input, currently, we don’t have too many customers using the Cortex-M to provide more feedback, so any input from you is highly appreciated.

Best Regards,

Hi @vix,

Article updated: How to load compiled binaries into Cortex-M | Toradex Developer Center


Let me know if you need anything else. Any feedback is appreciated.

If this topic is solved, can you please mark one of the answers above as solved?

Best Regards,

1 Like

One more question, how should I customize TorizonCore to do this?
As a general idea, I suppose:

  • add the firmware to TorizonCore filesystem
  • add steps to add the m4boot to the “bootcmd” alias

But how can I do this?
Customizing docker-compose.yml for the first step?
How to tweak u-boot?

Hi @vix,

I tested myself here and this can be accomplished with the image.json inside the image file. Since we’re dealing with U-Boot, TorizonCore Builder is not useful, since we can’t change U-Boot setting with it.

In this case, I simply added a few lines to my image.json and the u-boot-initial-end-sd files.

Here are the steps:

  • First, I downloaded an image from our feed, torizon-core-docker-verdin-imx8mm-Tezi_6.1.0+build.1 in my case, but you can choose a different one.
  • Next, I moved my Cortex-M binary inside this folder:
.rwxr-xr-x       6,260 hiago hiago 23 Mar 16:34  hello_world.bin
.rw-r--r--       1,566 hiago hiago 23 Mar 16:41  image.json
.rw-r--r--   1,314,376 hiago hiago 16 Jan 11:29  imx-boot
.rw-r--r--      79,368 hiago hiago 16 Jan 12:00  LA_OPT_NXP_SW.html
.rw-r--r--     212,480 hiago hiago 16 Jan 11:33  marketing.tar
.rw-r--r--         183 hiago hiago 16 Jan 11:33  prepare.sh
.rw-r--r--       2,488 hiago hiago 16 Jan 11:33  toradexlinux.png
.rw-r--r-- 217,362,695 hiago hiago 16 Jan 11:59  torizon-core-docker-verdin-imx8mm.ota.tar.zst
.rw-r--r--       4,232 hiago hiago 23 Mar 16:35  u-boot-custom-env-sd
.rw-r--r--       4,127 hiago hiago 16 Jan 10:04  u-boot-initial-env-sd
.rw-r--r--          18 hiago hiago 16 Jan 11:33  wrapup.sh
  • Now, you need to edit the image.json to copy the binary when Toradex Easy Installer is going to install your image.
--- image.json.old	2023-03-24 13:43:59.939387885 -0300
+++ image.json	2023-03-23 16:41:59.023515994 -0300
@@ -30,7 +30,8 @@
                         "filesystem_type": "ext4",
                         "mkfs_options": "-E nodiscard",
                         "filename": "torizon-core-docker-verdin-imx8mm.ota.tar.zst",
-                        "uncompressed_size": 599.375
+                        "uncompressed_size": 599.375,
+			"filelist": ["hello_world.bin:/ostree/deploy/torizon/var/"]

So in this case I’m telling Toradex Easy Installer to move m binary to /var/ when it’s going to install TorizonCore.

  • Finally, I added the variables to u-boot-initial-env-sd to automatically start the auxiliary core.
--- u-boot-initial-env-sd	2023-01-16 10:04:52.846678207 -0300
+++ u-boot-custom-env-sd	2023-03-23 16:35:53.519128019 -0300
@@ -1,4 +1,4 @@
-bootcmd=run bootcmd_mmc0
+bootcmd=run m4boot; run bootcmd_mmc0
@@ -53,5 +53,6 @@
 setup=setenv setupargs console=tty1 console=${console},${baudrate} consoleblank=0 earlycon
 update_uboot=askenv confirm Did you load flash.bin (y/N)?; if test "$confirm" = "y"; then setexpr blkcnt ${filesize} + 0x1ff && setexpr blkcnt ${blkcnt} / 0x200; mmc dev 0 1; mmc write ${loadaddr} 0x2 ${blkcnt}; fi
+m4boot=load mmc 0:1 ${loadaddr} 0x7e0000; cp.b ${loadaddr} 0x7e0000 10000; bootaux 0x7e0000;

In this case, I assume you’re using TCM memory, that’s why I used 0x7e0000.

After flashing the image, your binary will be in place and will also be started automatically.

I also did a test with the platform and did an OS update and the Cortex-M kept working. I’m not sure what’s going to happen if you do a bootloader remote update, because we need to make sure u-boot saves this custom m4boot variable.

Hope this helps.

Best Regards,

Hi @hfranco.tx
I’ve just seen a typo in the KB

(for Linux BSP) is written twice, and based on what I see, the first one should be (for Torizon).
Can you double check, please?

another question on the KB
The env variable loadaddr is never set explicitly, but it’s used (expanded) in other env variables.
printenv on the U-Boot shows that it’s set to 0x48200000.
What is this address? I see that it’s inside DDR region.
Should I care about its usage in Linux or in U-Boot? With the VF61 I had some overlapping between the memory used to load the firmware in M4 core and those used to display the splashscreen…

Hi @vix,

Thank you, you’re correct, the first one should be Torizon. I’ll correct that.

As u-boot on the verdin doesn’t allow a direct copy to the Cortex-M memory, we need to load the binary to RAM and then copy it to the Cortex-M memory. So this is just an intermediate area that shouldn’t cause any problems. This comes from NXP documentation, so I don’t have too many details on that.

Best Regards,

1 Like

Hi @hfranco.tx
I have two more questions on the steps detailed in the KB.

Question #1
Here I read

but, some lines below, I find

Is the right path from U-Boot point of view /boot/deploy/torizon/var/ or /ostree/deploy/torizon/var/ (/boot/ vs /ostree/)?
Or, are they the same?

Question #2
For Verdin iMX8M-Plus I read

setenv m4boot "${loadm4image}; cp.b ${loadaddr} 0x7e0000 ${m4image_size}; dcache flush; bootaux 0x7e0000"

as manual command.
If I run printenv m4boot from U-Boot I see
m4boot=ext4load mmc 2:1 0x48200000 /ostree/deploy/torizon/var/hello_world.bin; cp.b 0x48200000 0x7e0000 17556; dcache flush; bootaux 0x7e0000
In your message above, you suggest to modify u-boot-initial-env-sd

+m4boot=load mmc 0:1 ${loadaddr} 0x7e0000; cp.b ${loadaddr} 0x7e0000 10000; bootaux 0x7e0000;

It seems to me that the two m4boot are different.
I see load vs ext4load, but probably they’re the same.
I don’t see the path to hello_world.bin
I don’t see dcache flush

I would expect

+m4boot=ext4load mmc 2:1 ${loadaddr} ${m4image}; cp.b ${loadaddr} 0x7e0000 ${m4image_size}; dcache flush; bootaux 0x7e0000

Can you clarify, please?

Hi @vix,

Yes, TorizonCore uses OSTREE, so when you launch Linux you can’t see because it’s all handled by OSTREE and linux symbolic links (try to do a ls -lh /). But on u-boot, since ostree hasn’t launched yet, you need to pass the ostree folder.

You’re correct, ext4load and load are the same in this case, if you want to make sure you are running with the correct filesystem you can run ext4load.
Sorry, you are correct, I forgot about the path and flush, let me correct it here:

setenv m4boot "${loadm4image}; cp.b ${loadaddr} 0x7e0000 ${m4image_size}; dcache flush; bootaux 0x7e0000"

Where loadm4image and m4image_size are described in the article. Thanks for the correction.

Best Regards,

1 Like

Hi @hfranco.tx
I followed the steps to load the firmware from U-Boot and, while the boot process is halted at U-Boot, I can see all the messages from to Cortex-M to the UART4. I used a slightly modified hello_world example that uses the following while loop:

 while (1)
        for(unsigned int i=0; i<1000; ++i) {}
        PRINTF("hello world from uart4.\r\n");

But, as soon as the boot console shows

Starting kernel ...

the messages on the UART4 aren’t sent anymore.
And I don’t understand why.
For me it’s impossible to say if the Cortex-M firmware is running, is halted by a hardware error, is stopped, …
I have a doubt: I know tha the overlay verdin-imx8mp_hmp_overlay.dtbo reserves UART4 for Cortex-M.
But in this case the WiFi/BT module on the Verdin iMX8M-Plus won’t work.
Is it possible that another overlay in TorizonCore with evaluation containers override this setting for UART4, reserving it to Cortex-A and TorizonCore?

As I mentioned in another topic, I would like changing the Cortex-M UART from UART4 to UART2, but I would like doing it step by step.

Do you have any idea on whta happen?
What do you see on your Plus SoM?

Hi @vix,

I can reproduce the error and I believe I know why this is happening, let me try to explain.

On Verdin Plus Wifi/Bluetooth, the UART4 is used for communication with the Wifi/Bluet device. So, even though you disabled it on the device tree, the wifi driver is communicating with the wifi device (you can check the datasheet for more information), so the uart stops. I can verify the Cortex-M is still running without issues, the only problem is that the uart debug is not working anymore (or it’s owned by the wifi driver now).

Checking the remote proc interface, I can see the Cortex-M is attached (and running):

torizon@verdin-imx8mp-14777535:~$ cat /sys/class/remoteproc/remoteproc0/state 

How do I know it’s still running? In my case, I set an LED to blink, so I know it’s still running (you can do the same with the RPMSG, for example).

So, the workaround here is to disable the wifi completely (including all the drivers and kernel modules), which I don’t recommend and I don’t think it’s a good idea, or change the interface that Cortex-M is using to communicate (as you mentioned, using UART2 for example).

This should be achievable by changing the Cortex-M code to use the other UART, instead of UART4.

Best Regards,

This should be achievable by changing the Cortex-M code to use the other UART, instead of UART4.

I tried to do this, but without success (up to now).
I used MCUXpresso Config Tools to configure UART2, then I changed board.h defines, but no output from UART2.
I use the following pins on Verdin Development Board
that should be connected to

and at the end to

I connected an oscilloscope probe to X16 jumpers 24 and 25 (closed) , but no activity on the signals.

EDIT: but if I send strings to UART2 from TorizonCore, I can see them on UART2.
So, I suspect a proper dto is necessary (to reserve UART2 to Cortex-M).

I rebuilt a TorizonCore image with a dts that disables UART2 (the same as verdin-imx8mp_hmp_overlay.dts, but with uart2 instead of uart4):

// SPDX-License-Identifier: GPL-2.0-or-later OR MIT
 * Copyright 2023 Toradex

/* Enable RPMSG and the RemoteProc M7 driver.
 * Note: This overlay is working only on nonwifi modules. For more information, please
 * check the Verdin iMX8MP Datasheet section 5.4 Wi-Fi and Bluetooth.


#include <dt-bindings/clock/imx8mp-clock.h>

/ {
	compatible = "toradex,verdin-imx8mp";

&{/} {
	imx8mp-cm7 {
		compatible = "fsl,imx8mp-cm7";
		clocks = <&clk IMX8MP_CLK_M7_DIV>;
		mbox-names = "tx", "rx", "rxdb";
		mboxes = <&mu 0 1
			  &mu 1 1
			  &mu 3 1>;
		memory-region = <&vdevbuffer>, <&vdev0vring0>, <&vdev0vring1>, <&rsc_table>, <&m7_reserved>;
		rsc-da = <0x55000000>;
		syscon = <&src>;
		fsl,startup-delay-ms = <500>;

&i2c3 {
	status = "disabled";

&pwm4 {
	status = "disabled";

&resmem {
	#address-cells = <2>;
	#size-cells = <2>;

	m7_reserved: m7@0x80000000 {
		reg = <0 0x80000000 0 0x1000000>;

	vdev0vring0: vdev0vring0@55000000 {
		reg = <0 0x55000000 0 0x8000>;

	vdev0vring1: vdev0vring1@55008000 {
		reg = <0 0x55008000 0 0x8000>;

	vdevbuffer: vdevbuffer@55400000 {
		compatible = "shared-dma-pool";
		reg = <0 0x55400000 0 0x100000>;

	rsc_table: rsc_table@550ff000 {
		reg = <0 0x550ff000 0 0x1000>;

&sai3 {
	status = "disabled";

&sdma3 {
	status = "disabled";

&uart2 {
	status = "disabled";

and rebuilt the Cortex-M firmware so that it uses UART2.

But now the TorizonCore boot fails, with the following log:

U-Boot 2022.04-6.3.0-devel+git.a7d6442796bf (Jan 01 1970 - 00:00:00 +0000)

CPU:   i.MX8MP[8] rev1.1 1600 MHz (running at 1200 MHz)
CPU:   Industrial temperature grade (-40C to 105C) at 57C
Reset cause: POR
DRAM:  4 GiB
Core:  89 devices, 23 uclasses, devicetree: separate
WDT:   Started watchdog@30280000 with servicing (60s timeout)
Loading Environment from MMC... OK
In:    serial@30880000
Out:   serial@30880000
Err:   serial@30880000
Model: Toradex 0058 Verdin iMX8M Plus Quad 4GB WB IT V1.1A
Serial#: 14777722
Carrier: Toradex Verdin Development Board V1.1B, Serial# 10893470
Net:   eth1: ethernet@30be0000, eth0: ethernet@30bf0000 [PRIME]
Normal Boot
Hit any key to stop autoboot:  0
17620 bytes read in 2 ms (8.4 MiB/s)
## Starting auxiliary core stack = 0x20020000, pc = 0x0000048D...
switch to partitions #0, OK
mmc2(part 0) is current device
Scanning mmc 2:1...
Found U-Boot script /boot.scr
973 bytes read in 0 ms
## Executing script at 50280000
6680 bytes read in 2 ms (3.2 MiB/s)
90156 bytes read in 3 ms (28.7 MiB/s)
49 bytes read in 2 ms (23.4 KiB/s)
Applying Overlay: verdin-imx8mp_hmp_cemb_overlay.dtbo
2503 bytes read in 3 ms (814.5 KiB/s)
13348705 bytes read in 43 ms (296.1 MiB/s)
11720397 bytes read in 38 ms (294.1 MiB/s)
   Uncompressing Kernel Image
## Flattened Device Tree blob at 50200000
   Booting using the fdt blob at 0x50200000
   Using Device Tree in place at 0000000050200000, end 0000000050239fff

Starting kernel ...

[    0.874604] Internal error: synchronous external abort: 96000210 [#1] PREEMPT SMP
[    0.882104] Modules linked in:
[    0.885164] CPU: 3 PID: 1 Comm: swapper/0 Not tainted 5.15.77-6.3.0-devel+git.95b4e4bb8a10 #1-TorizonCore
[    0.894740] Hardware name: Toradex Verdin iMX8M Plus WB on Verdin Development Board (DT)
[    0.902836] pstate: 60000005 (nZCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[    0.909806] pc : imx_uart_probe+0x31c/0x7d0
[    0.914002] lr : imx_uart_probe+0x30c/0x7d0
[    0.918193] sp : ffff80000a28bb60
[    0.921508] x29: ffff80000a28bb60 x28: 0000000000000000 x27: ffff800009a204c0
[    0.928657] x26: ffff0000c4cf4000 x25: 00000000fffffffa x24: 00000000fffffffa
[    0.935804] x23: ffff0000c05a7010 x22: 000000000000002b x21: ffff0000c05a7000
[    0.942955] x20: 0000000000000000 x19: ffff0000c4ccb880 x18: 0000000000000001
[    0.950105] x17: 343131206d6f7266 x16: 2065746172206475 x15: 0000000000000000
[    0.957255] x14: ffffffffffffffff x13: 0000000000000018 x12: 0101010101010101
[    0.964403] x11: 0000000000000020 x10: 0101010101010101 x9 : 0000000000000004
[    0.971550] x8 : 0101010101010101 x7 : 7f7f7f7f7f7f7f7f x6 : fffefefefeff6f6c
[    0.978700] x5 : 8080808080800000 x4 : 0000000000000010 x3 : 0000000000000000
[    0.985849] x2 : 0000000000000000 x1 : ffff80000aad0080 x0 : 0000000000000000
[    0.992998] Call trace:
[    0.995446]  imx_uart_probe+0x31c/0x7d0
[    0.999288]  platform_probe+0x68/0xe0
[    1.002954]  really_probe+0xbc/0x46c
[    1.006540]  __driver_probe_device+0x114/0x190
[    1.010993]  driver_probe_device+0x40/0x100
[    1.015183]  __driver_attach+0xac/0x210
[    1.019031]  bus_for_each_dev+0x70/0xd0
[    1.022876]  driver_attach+0x24/0x30
[    1.026461]  bus_add_driver+0x14c/0x250
[    1.030305]  driver_register+0x78/0x130
[    1.034153]  __platform_driver_register+0x28/0x34
[    1.038865]  imx_uart_init+0x3c/0x64
[    1.042450]  do_one_initcall+0x44/0x290
[    1.046291]  kernel_init_freeable+0x228/0x2b0
[    1.050660]  kernel_init+0x24/0x12c
[    1.054158]  ret_from_fork+0x10/0x20
[    1.057744] Code: 2a0003f4 35001820 f9400a61 91020021 (b9400021)
[    1.063863] ---[ end trace 3d80d5f6ea895993 ]---
[    1.068488] Kernel panic - not syncing: synchronous external abort: Fatal exception
[    1.076152] SMP: stopping secondary CPUs
[    1.080090] Kernel Offset: disabled
[    1.083581] CPU features: 0x00002001,20000846
[    1.087944] Memory Limit: none
[    1.091003] Rebooting in 5 seconds..

It seems some failue in imx_uart_
@hfranco.tx do you have any idea?

Hi @vix,

I was able to reproduce the issue on my side and I wasn’t able to change the uart yet.
The cortex-M should support changing the pins to a different one, but I couldn’t figure out what’s happening yet.

I’m searching the NXP community to check if I can find anything that helps. I’ll keep you updated on any changes.

Best Regards,

Hi @hfranco.tx
do you have news on this topic?

I’ve opened a ticket to NXP tech support, but no answer yet…
I hope Toradex has better channles to NXP

Hi @vix,

I made some tests and made an update to this thread: Verdin IMX8MM, hardfault when initializing UART on M4 core - #9 by hfranco.tx

Please check my last answer. I’m seeing the exact same thing on the Plus, I’m trying to check what is causing this issue with my JTAG probe now.

I’ll update you here and in the other ticket if I’m able to make progress.

Best Regards,

Hi @vix,

Please check my answer here:

Best Regards,

Hello @hfranco.tx
thank you for your investigation on the Mini.
It’s useful to me to understand what happens on the Plus.
And the situation is a little bit more complicated.
I try to describe what I see on the Plus:

  • I halt the boot into U-Boot so that I can use Segger JTAG debug probe

  • the “hello world” supplied in the NXP SDK works as expected using UART4

  • I work with TorizonCore

  • on the Verdin iMX8M-Plus, UART_2 (SODIMM_139 or UART_2_TXD) is connected to UART2 of the iMX8M-Plus processor

  • your information is useful because I missed the clock configuration in void BOARD_BootClockRUN(void)

    CLOCK_SetRootMux(kCLOCK_RootUart4, kCLOCK_UartRootmuxSysPll1Div10); /* Set UART source to SysPLL1 Div10 80MHZ */
    CLOCK_SetRootDivider(kCLOCK_RootUart4, 1U, 1U);                     /* Set root clock to 80MHZ/ 1= 80MHZ */
  • I compared the “hello world” example from NXP SDKs of Mini and Plus processor and I found the following difference:
    For the Mini

    void BOARD_InitDebugConsole(void)
       uint32_t uartClkSrcFreq = BOARD_DEBUG_UART_CLK_FREQ;

    For the Plus

    void BOARD_InitDebugConsole(void)
       uint32_t uartClkSrcFreq = BOARD_DEBUG_UART_CLK_FREQ;

    Note that CLOCK_EnableClock() is missing in the Plus SDK.
    And here comes the first doubt: should I add these function, or not?
    I tried both the options, loading the firmware through JTAG, and I always see HardFault, arised from

    I see that BFARVALID it’s set to 1 and so it’s a precise fault coming from access to 0x30890080

    The offending address is inside AIPS3 Memory Map for UART2

    and I suspect some mis-configuration.

    And here comes the second, bigger, doubt:
    In the Mini SDK I see a complete different void BOARD_InitMemory(void)

So I need to investigate…
If you have ideas or suggestions, fell free to share