Adding additional DTB files via TorizonCore builder

I’m poking around some of the devicetree options and I learned that the devicetree file used by the SoM is controlled by the uboot fdtfile uboot environment variable.

When using torizoncore-builder with custom dts, it would appear that it overrides one of the default dtb files with the requisite changes.

I am curious whether it is possible to “add” additional DTB files to the system using TCB, and if so, where should they be placed?

For example, lets say I need to make a hardware change on a carrier board that is not strictly compatible, but would like to avoid having to create two separate updates and OS builds. Would it be possible to do something like the following:

  • Have a “safe” default dtb as consumed by torizoncore-builder
  • Place additional dtbs as a filesystem->customization, e.g. imx8mp-nonwifi-verdin-variantA.dtb, … variantB.dtb
  • Have a script that detects the hardware accordingly and then modifies the uboot environment variable fdt_board appropriately, to either variantA or variantB.

Based on other threads here, this sounds possible (How automatic dtb selection works?) but the piece of the puzzle I am missing is where to place any additional DTB files so that they would appear in the correct location on the filesystem, e.g. /boot/ostree/torizon-[commit]/dtb/ (?)

Greetings @bw908,

When using torizoncore-builder with custom dts, it would appear that it overrides one of the default dtb files with the requisite changes.

It’s actually a bit more complicated then that. So when TorizonCore Builder deploys a new DTS file, it does so to do this location in the filesystem:

# test.dtb is the new device tree I deployed
torizon@apalis-imx6-05228985:~$ ls /usr/lib/modules/5.4.193-5.7.0+git.f5d73fd6e9f8/dtb/
imx6q-apalis-eval.dtb  imx6q-apalis-ixora-v1.1.dtb  overlays  overlays.txt  test.dtb

When OSTree initializes the new deployment it then creates a copy of these device tree files in the location you are familiar with:

torizon@apalis-imx6-05228985:~$ ls /boot/ostree/torizon-ee10f9fd418bae0e9674b4b3bd6edb3b732e00cc9a0df318da42e79d2f8a657a/dtb/
imx6q-apalis-eval.dtb  imx6q-apalis-ixora-v1.1.dtb  overlays  overlays.txt  test.dtb

Then as a final step TorizonCore Builder hardcodes the new device tree file in the boot script so that it is chosen:

Boot Script

torizon@apalis-imx6-05228985:~$ cat /usr/lib/ostree-boot/uEnv.txt

set_bootargs=env set bootcmd_args env set bootargs ${defargs} root=LABEL=otaroot rootfstype=ext4 ${bootargs} ${tdxargs}

set_kernel_load_addr=if test ${kernel_image_type} = “Image.gz”; then
env set kernel_addr_load ${loadaddr};
env set bootcmd_unzip_k ‘unzip $kernel_addr_load $kernel_addr_r’;
env set kernel_addr_load ${kernel_addr_r};
env set bootcmd_unzip_k ‘;’;
fi || true

load_overlay=load ${devtype} ${devnum}:${otaroot} ${loadaddr} /boot${fdtdir}/${overlays_file}; env import -t ${loadaddr} ${filesize}
apply_overlays=fdt addr ${fdt_addr_r} && fdt resize 0x20000 &&
for overlay_file in ${fdt_overlays}; do
echo “Applying Overlay: ${overlay_file}” &&
load ${devtype} ${devnum}:${otaroot} ${loadaddr} /boot${fdtdir}/overlays/${overlay_file} && fdt apply ${loadaddr};

bootcmd_load_k=load ${devtype} ${devnum}:${otaroot} ${kernel_addr_load} “/boot”${kernel_image}
bootcmd_load_r=load ${devtype} ${devnum}:${otaroot} ${ramdisk_addr_r} “/boot”${ramdisk_image}; env set ramdisk_size ${filesize}

check kernel_image2 to avoid booting from other then default emmc in case of

bootlimit is reached and there is no other deployed image

check_rollback_needed=if test -n “${kernel_image2}” && test “${rollback}” = “1”; then
echo “Rollback enabled. Booting previously deployed version.”;
env set kernel_image ${kernel_image2};
env set ramdisk_image ${ramdisk_image2};
env set bootargs ${bootargs2};
env set fdtdir ${fdtdir2};
env set fdt_file ${fdt_file2};
env set fdtfile ${fdtfile2};
fi || true

set_fdt_path=if test -n “${fdtdir}”; then
env set fdt_path /boot${fdtdir}/${fdtfile};
env set fdt_path /boot${fdt_file};
fi || true

bootcmd_dtb=load ${devtype} ${devnum}:${otaroot} ${fdt_addr_r} ${fdt_path};
if test ${skip_fdt_overlays} != 1; then
if test -e ${devtype} ${devnum}:${otaroot} /boot${fdtdir}/${overlays_file}; then
run load_overlay && run apply_overlays || true;
fi || true;
fi || true

set_bootargs_custom=if test -n “${fdt_overlays}”; then
for overlay_file in ${fdt_overlays}; do
if test “${overlay_file}” = “custom-kargs_overlay.dtbo”; then
if fdt get value custom_kargs /chosen/ bootargs_custom; then
env set bootargs ${bootargs} ${custom_kargs};
fi || true

board_fixups=if test “${board}” = “verdin-imx8mm”; then
if test “${fdtfile}” = “imx8mm-verdin-nonwifi-v1.1-dahlia.dtb”; then
env set fdtfile imx8mm-verdin-nonwifi-dahlia.dtb;
elif test “${fdtfile}” = “imx8mm-verdin-nonwifi-v1.1-dev.dtb”; then
env set fdtfile imx8mm-verdin-nonwifi-dev.dtb;
elif test “${fdtfile}” = “imx8mm-verdin-wifi-v1.1-dahlia.dtb”; then
env set fdtfile imx8mm-verdin-wifi-dahlia.dtb;
elif test “${fdtfile}” = “imx8mm-verdin-wifi-v1.1-dev.dtb”; then
env set fdtfile imx8mm-verdin-wifi-dev.dtb;
fi || true

bootcmd_boot=bootz ${kernel_addr_r} ${ramdisk_addr_r}:${ramdisk_size} ${fdt_addr_r}
bootcmd_run=run board_fixups && run check_rollback_needed && run set_bootargs && run set_fdt_path &&
run bootcmd_dtb && run bootcmd_args && run set_bootargs_custom && run set_kernel_load_addr &&
run bootcmd_load_k && run bootcmd_unzip_k && run bootcmd_load_r && run bootcmd_boot

This effectively overwrites whatever fdtfile is set as in the U-boot environment.

Now back to your overall question. TorizonCore Builder is only designed to deploy one new custom device tree at a time. If you attempt to deploy a second after the first, the first one will be removed from the deployment in favor of the 2nd one. To deploy multiple new device trees at once currently you’d either need to do a Yocto build that does this. Or you could inject new device tree files using raw OSTree commands similar to how TorizonCore Builder does under the hood, though this could be messy.

As for choosing which device tree you want to use on boot. As you said you’d need custom U-Boot and/or boot script logic in order to do this. To modify the U-Boot environment or the boot script in a free-form way is currently outside of the capabilities of TorizonCore Builder. Furthermore, U-Boot lives outside of OSTree so this really only leaves Yocto as a viable option to customizing this.

Now with all that said, it would appear that you’d need at a minimum a custom image that has all your device tree variants deployed along with custom boot logic to choose and load the correct device tree file. With this in theory, you could have a single image for multiple hardware variants. I’m not sure if this is particularly “simpler” or “easier” than having different images for different hardware variants.

Best Regards,

Thanks for the information. Though based on reading some of these things and the documentation here it seems like this would be possible with overlays instead - Device Tree Overlays (Linux) | Toradex Developer Center - in other words, the differences between variantA and variantB would become separate overlay files that are later dynamically applied to overlays.txt by some sort of hardware detection script.

There isn’t an immediate need for this capability on my part - just something I’m trying to understand as a means of future-proofing a design given past experience with supply chain turmoil and parts being obsoleted.

In our case these devices will generally be receiving offline-only updates so the size of the updates is of value to us - the devices will be provisioned accordingly so that we can identify them carrier board type should they need to have different OS builds in the future, but we’re aiming for size and compatibility foremost because these systems are also effectively a “family” in which there is significant overlap in the hardware and software. Think “widget” and “widget + display”, and the scenario I’m trying to assess is “what happens if [display hardware] needs to be replaced with something requiring device-tree changes” in the middle of the product lifecycle.

As an example let’s say initially you have hardware A and B that respectively require overlays A and B. You would create a single image that containers both overlays, and then devise some kind of logic that can dynamically choose which overlay to apply based on the actual hardware. Is that more or less your idea?

Then say in later in the future you now have hardware C. You would then create a new version of the image that has overlays A, B and C. Then you would apply the updates to the already deployed devices as needed. Is that more or less the approach you’re going for?

Best Regards,

Yes, that’s the general idea I’m going for.

If you can devise the logic for dynamically picking and choosing which overlay to apply for your hardware then I don’t see any major issues.

1 Like

Happy to report my idea worked. Setup is as follows:

  • Common base DTS file with all hardware defined. Items unique to one or the other configuration are disabled by default.
  • Created a pair of DTS files that change the status for each variant’s additional hardware to “okay”.
  • Build these into dtbo files using dtc
  • Place the dtbos in usr/lib/modules/[version]/dts/overlays
  • Add a systemd script that checks the contents of overlays.txt matches the hardware (we use some GPIO strapping to do this). If not, rewrite the .txt and issue a reboot.
  • Can be easily expanded with additional variants/hardware revisions if necessary.

Something similar can be done with uboot scripts as well but this is far easier and can be done directly via torizoncore-builder.


I’ll just add an additional update here that this procedure may not play nicely with updates since the update will activate the script and trigger a second reboot in short succession after the initial update.

To handle this one should modify the ostree-pending-reboot systemd service with an ExecStartPre hook to call a script to manage the overlays before the update reboot is triggered.

1 Like

Ah yes that makes sense that would happen. Well thank you for coming back and sharing the update to your workaround.