- [Sitara AM67A U-Boot](#sitara-am67a-u-boot)
  - [Note on git-buildpackage](#note-on-git-buildpackage)
  - [Boot source selection](#boot-source-selection)
    - [SD-Card on BeagleY-AI](#sd-card-on-beagley-ai)
    - [eMMC on BVS-MA](#emmc-on-bvs-ma)
    - [DFU on BVS-MA](#dfu-on-bvs-ma)
      - [Flash eMMC via DFU](#flash-emmc-via-dfu)
    - [Boot via Network](#boot-via-network)
      - [Setup TFTP server](#setup-tftp-server)
      - [Setup NFS server](#setup-nfs-server)
      - [Boot process on board](#boot-process-on-board)
      - [NFS troubleshooting](#nfs-troubleshooting)
  - [U-Boot environment](#u-boot-environment)
    - [Environment setup](#environment-setup)
    - [Control environment](#control-environment)
  - [Get the latest version](#get-the-latest-version)
  - [Build from source for testing](#build-from-source-for-testing)
    - [Apply the patches](#apply-the-patches)
    - [Get dependencies](#get-dependencies)
    - [Build](#build)
    - [Deploy](#deploy)
      - [SD-Card Boot](#sd-card-boot)
      - [eMMC Boot](#emmc-boot)
  - [Build and package for deployment](#build-and-package-for-deployment)
  - [Upgrade to new upstream release](#upgrade-to-new-upstream-release)
  - [Important adaptions](#important-adaptions)
  - [Known issues](#known-issues)
  - [Resources](#resources)

# Sitara AM67A U-Boot

The following documentation aims to document everything you need to know to build and deploy U-Boot for the Sitara AM67A platform.

## Note on git-buildpackage

To be license compliance we should be able to keep upstream sources and the changes from our side separated.

There are different ways how to reach that goal, but at least in case of bigger projects like U-Boot we decided to go with the common `git-buildpackage` tool which makes it convenient to work with quilt patches in a Git workflow.

You can simply install it via the following command.

`sudo apt install git-buildpackage`

To make that working we need to separate the branch with our changes, so with the `debian/` directory hierarchy separate to the upstream branch.

In case of U-Boot the gitflow can look like the following.

![gitGraph
   commit tag: "v2024.04"
   branch debian/am64x
   checkout debian/am64x
   commit
   branch feature/support_nfs
   checkout feature/support_nfs
   commit
   commit
   commit
   checkout debian/am64x
   merge feature/support_nfs
   commit tag: "debian/am64x/2024.04-01"
   branch bug/fix_dfu
   checkout bug/fix_dfu
   commit
   commit
   checkout debian/am64x
   merge bug/fix_dfu
   commit tag: "debian/am64x/2024.04-02"
   checkout main
   commit tag: "v2024.07"
   checkout main
   commit tag: "v2025.01"
   branch debian/am67a
   checkout debian/am67a
   commit
   branch feature/fix_cve-2024-30767
   checkout feature/fix_cve-2024-30767
   commit
   checkout debian/am67a
   merge feature/fix_cve-2024-30767
   commit tag: "debian/am67a/2025.01-01"](./assets/git-buildpackage-gitflow.png)

## Boot source selection

On the BeagleY-AI you boot by default from SD-Card. To boot via network please recognized the information in the following section.

On the BVS-MA you can either boot from eMMC or via DFU (USB peripheral mode), the boot via network is only possible in theory because currently the used PCIe network interface is not supported in U-Boot.

### SD-Card on BeagleY-AI

On BeagleY-AI U-Boot will try to boot from SD-Card.

You can check with `lsblk` and `df` if everything was properly recognized and booted from SD-Card.

```sh
balluff@beagley-ai:~$ lsblk
NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
mmcblk1     179:0    0 59.5G  0 disk 
├─mmcblk1p1 179:1    0  250M  0 part /boot/u-boot
├─mmcblk1p2 179:2    0    2G  0 part /mnt/root-ro
├─mmcblk1p3 179:3    0    2G  0 part 
├─mmcblk1p4 179:4    0    1K  0 part 
└─mmcblk1p5 179:5    0 55.2G  0 part /data

balluff@beagley-ai:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       2.0G  650M  1.2G  36% /
devtmpfs        1.5G     0  1.5G   0% /dev
tmpfs           1.9G     0  1.9G   0% /dev/shm
tmpfs           759M  9.0M  750M   2% /run
tmpfs           5.0M     0  5.0M   0% /run/lock
/dev/mmcblk1p1  250M  4.6M  246M   2% /boot/u-boot
/dev/mmcblk1p5   55G   84K   52G   1% /data
tmpfs           380M     0  380M   0% /run/user/0
```

### eMMC on BVS-MA

**Note:** Due to the current hardware configuration we work with `MMCSD Boot` mode instead of the "real" `eMMC Boot` mode. In that mode the eMMC is handled like an SD-Card, so only the UDA (user data area) is used and the hardware boot partitions are not used. So the bootloader is stored in the user data area of the eMMC, in our case in a separate `vfat` boot partition. This might change in the future.

On BVS-MA U-Boot will try to boot from eMMC when the alternative boot mode USB (DFU) is not actively selected.

You can check with `lsblk` and `df` if everything was properly recognized and booted from eMMC.

```sh
balluff@bvs-ma:~$ lsblk
NAME         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
mmcblk0      179:0    0  7,3G  0 disk
├─mmcblk0p1  179:1    0  250M  0 part /boot/u-boot
├─mmcblk0p2  179:2    0    2G  0 part /mnt/root-ro
├─mmcblk0p3  179:3    0    2G  0 part
├─mmcblk0p4  179:4    0    1K  0 part
└─mmcblk0p5  179:5    0    3G  0 part /data
mmcblk0boot0 179:32   0    4M  1 disk
mmcblk0boot1 179:64   0    4M  1 disk

balluff@bvs-ma:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay         2,9G  3,2M  2,8G   1% /
udev            1,9G     0  1,9G   0% /dev
tmpfs           1,9G     0  1,9G   0% /dev/shm
tmpfs           381M  8,8M  372M   3% /run
tmpfs           5,0M     0  5,0M   0% /run/lock
/dev/mmcblk0p2  2,0G  1,3G  559M  70% /mnt/root-ro
/dev/mmcblk0p1  250M  4,4M  246M   2% /boot/u-boot
/dev/mmcblk0p5  2,9G  3,2M  2,8G   1% /data
tmpfs           381M  4,0K  381M   1% /run/user/0
```

### DFU on BVS-MA

Booting via DFU (Device Firmware Upgrade) is possible on BVS-MA by selecting the alternative boot mode by placing the Jumper before doing the power-on.
This mode allows to flash the eMMC directly via USB (peripheral mode).

DFU has mainly the target to enable the user to flash certain targets like eMMC or eeprom of the board.

More infos about DFU in U-Boot: [docs.u-boot.org -DFU](https://docs.u-boot.org/en/latest/usage/dfu.html)

Before you can start you need to have the `dfu-util` installed on your Host system as also a connection to the target board via UART and to the USB peripheral port.

Also you need to have the bootloader artifacts ready on your host system.

To get the bootloader artifacts you can simply retrieve the latest version of the bootloader `.deb` package from our APT server [apt.balluff.systems/balluff-linux/ - u-boot-balluff-am67a](http://apt.balluff.systems/balluff-linux/pool/main/u/u-boot-balluff-am67a/) or compile it own your own like described in the section [Build from source for testing](#build-from-source-for-testing).

In case you used the `.deb` package you need to extract the package content and switch to the subdir `boot/u-boot/` to get access to the relevant binaries.

After that is prepared you can power on the Target board and follow the instructions in the code block below.

```sh
# First validate that the Target is detected
$ sudo dfu-util -l
dfu-util 0.11

Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2021 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

Found DFU: [0451:6165] ver=0200, devnum=15, cfg=1, intf=0, path="3-2", alt=1, name="SocId", serial="01.00.00.00"
Found DFU: [0451:6165] ver=0200, devnum=15, cfg=1, intf=0, path="3-2", alt=0, name="bootloader", serial="01.00.00.00"
```

If you can see the DFU target with the name `bootloader` you can start bringing the board up in four steps.

```sh
# First transmit the tiboot3.bin artifact
$ sudo dfu-util -R -a bootloader -D tiboot3.bin
```

Now you should see a different output when invoking the list command.

```sh
Found DFU: [0451:6165] ver=0224, devnum=15, cfg=1, intf=0, path="1-2", alt=1, name="u-boot.img", serial="UNKNOWN"
Found DFU: [0451:6165] ver=0224, devnum=15, cfg=1, intf=0, path="1-2", alt=0, name="tispl.bin", serial="UNKNOWN"
```

If you see the two DFU targets you can transmit the SPL and u-boot image.

```sh
# Send SPL
$ sudo dfu-util -R -a tispl.bin -D tispl.bin

# Send U-Boot
$ sudo dfu-util -R -a u-boot.img -D u-boot.img

# When this command completed successfully you should stop
# the auto-boot inside U-Boot via the UART connection
```

After stopping auto-boot you should have a working U-Boot shell to flash for example targets like eMMC and the eeprom.

#### Flash eMMC via DFU

For flashing the eMMC via DFU there are two approaches. The first one would be to flash a whole image (including different partitions and the partition table) in raw mode or to flash single filesystems on already existing partitions.

**Full flash in raw-mode:**

The simplest way of flashing the eMMC via DFU is to use the raw flash mode, because with that you save the time for partitioning and preparing the single filesystems.

One downside you have is that it takes quite some time due to the limited performance of DFU.

But for a first flash it can be worth doing it this way.

You can set directly the `dfu_alt_info` and start DFU mode.

```sh
# Set the dfu_alt_info
=> setenv dfu_alt_info "userarea raw 0 0x1000000 mmcpart 0"
# In the example the size is limited to 8GB (0x1000000)
# Please assure that the given size is bigger than the image
# but not greater than the eMMC

# Start DFU mode
=> dfu 0 mmc 0
```

Now you can transfer the decompressed and unpacked image via DFU from Host to Target.

```sh
$ sudo dfu-util -a userarea -D b0ed83396-dev_20260216_bvs-ma_emmc_lite_ro.img
```

Please note, that the process is quite slow.

**Filesystem flash:**

When you want to flash the single filesystems you need to assure that there are partitions prepared.

You can do the partitioning within U-Boot via the `mbr` util like described below.

**Note:** When we switch from `MMCSD Boot` mode (UDA) to the "real" `eMMC Boot` mode (using the hardware boot partitions) the partition layout will change. So please check if the partition layout below matches the layout of the image.

```sh
# Define the partition layout in some variable
# Please check the partition sizes, they need to match your image!
=> setenv partitions 'name=boot,start=4M,size=250M,id=0x0C;name=rfsa,size=2048M,id=0x83;name=rfsb,size=2048M,id=0x83;name=[ext],size=-,id=0x05;name=data,size=2048M,id=0x83'

# Write partition layout to eMMC
=> mbr write mmc 0 ${partitions}
MBR: write success!

# Verify written partition layout
=> mbr verify mmc 0 ${partitions}
MBR: verify success!

# You can also check the partition table
=> mmc dev 0
=> mmc list
mmc@fa10000: 0 (eMMC)
=> mmc part
Partition Map for MMC device 0  --   Partition Type: DOS

Part    Start Sector    Num Sectors     UUID            Type
  1     8192            512000          aa4c7015-01     0c Boot
  2     520192          4194304         aa4c7015-02     83
  3     4714496         4194304         aa4c7015-03     83
  4     8908800         6361088         aa4c7015-04     05 Extd
  5     8910848         6359040         aa4c7015-05     83
```

When the eMMC is partitioned you can set `dfu_alt_info` and start DFU mode.

```sh
# Set the dfu_alt_info
=> setenv dfu_alt_info 'boot part 0 1; rfsa part 0 2; rfsb part 0 3; data part 0 5'

# Start dfu mode
=> dfu 0 mmc 0
```

When DFU mode was started successfully you can start flashing the filesystems from the Target.

```sh
# Verify that targets are visible
$ sudo dfu-util -l
dfu-util 0.11

Copyright 2005-2009 Weston Schmidt, Harald Welte and OpenMoko Inc.
Copyright 2010-2021 Tormod Volden and Stefan Schmidt
This program is Free Software and has ABSOLUTELY NO WARRANTY
Please report bugs to http://sourceforge.net/p/dfu-util/tickets/

Found DFU: [0451:6165] ver=0224, devnum=19, cfg=1, intf=0, path="3-2", alt=3, name="data", serial="0000000000000000"
Found DFU: [0451:6165] ver=0224, devnum=19, cfg=1, intf=0, path="3-2", alt=2, name="rfsb", serial="0000000000000000"
Found DFU: [0451:6165] ver=0224, devnum=19, cfg=1, intf=0, path="3-2", alt=1, name="rfsa", serial="0000000000000000"
Found DFU: [0451:6165] ver=0224, devnum=19, cfg=1, intf=0, path="3-2", alt=0, name="boot", serial="0000000000000000"

# Example: Transfer rfsa image
$ sudo dfu-util -a rfsa -D b0ed83396-dev_20260216_bvs-ma_emmc_lite_ro_rfsa.img
```

The transfer can take quite some time depending on the image size. Although you are working with sparse image, DFU seems to transfer everything with a fixed blocksize without skipping null blocks. So consider either network boot or manually creating filesystem images which are as small as possible.

For the data partition you may need to create a image manually upfront.

The process in general of generating an filesystem image manually can be seen below.

```sh
# Approach 1: Extract partitions from full image
# This approach is easy and straight forward but has the downside that you need
# to transmit images with have the whole partition size.
# So in case of data you need to transmit 3GB instead of the 11MB which are actually used (~30 min).

# Extract the image
$ tar xfz b0ed83396-dev_20260216_bvs-ma_emmc_lite_ro.img.tar.gz

# Create a loopdevice from the image
$ sudo losetup --find --partscan --show b0ed83396-dev_20260216_bvs-ma_emmc_lite_ro.img
/dev/loop28

# Extract the first partition (rfsa)
$ sudo dd if=/dev/loop28p1 of=rfsa.ext4 conv=sparse bs=1M
2048+0 records in
2048+0 records out
2147483648 bytes (2,1 GB, 2,0 GiB) copied, 0,362826 s, 5,9 GB/s

# Extract the second partition (data)
$ sudo dd if=/dev/loop28p5 of=data.ext4 conv=sparse bs=1M
3071+0 records in
3071+0 records out
3220176896 bytes (3,2 GB, 3,0 GiB) copied, 1,36951 s, 2,4 GB/s

# The files should be created like the following
$ ls -lhs *.ext4
 11M -rw-r--r-- 1 root   root   3,0G Feb 16 11:10 data.ext4
690M -rw-r--r-- 1 root   root   2,0G Feb 16 11:10 rfsa.ext4

# Approach 2: Generate images which are as small as possible
# This approach has the benefit of saving a lot of time, when the actual image size
# is way smaller than the image size. So DFU doesn't need to transmit to much empty blocks.

# Create a empty image with minimum possible size (e.g. 750MB for rfsa and 30MB for data)
$ dd if=/dev/null of=rfsa.ext4 bs=1M seek=750
$ dd if=/dev/null of=data.ext4 bs=1M seek=30
$ dd if=/dev/null of=boot.vfat bs=1M seek=250

# Write filesystem
$ mkfs.ext4 -F rfsa.ext4
$ mkfs.ext4 -F data.ext4
$ mkfs.vfat -F 32 boot.vfat

# Mount prepared empty filesystems
$ mkdir mnt_rfsa
$ mkdir mnt_data
$ mkdir mnt_boot
$ sudo mount -t ext4 rfsa.ext4 mnt_rfsa
$ sudo mount -t ext4 data.ext4 mnt_data
$ sudo mount -t vfat boot.vfat mnt_boot

# Now copy the files into the images
$ sudo cp <rfsa-files> mnt_rfsa
$ sudo cp <data-files> mnt_data
$ sudo cp <boot-files> mnt_boot

# or unpack an archive into the image
$ sudo tar -xvf <rfsa-archive> mnt_rfsa

# Make sure files are written from cache
$ sudo sync

# Unmount images
$ sudo umount mnt_rfsa
$ sudo umount mnt_data
$ sudo umount mnt_boot

# ---

# For both approaches just transfer the generated image files after DFU mode was started
$ sudo dfu-util -a rfsa -D rfsa.ext4
$ sudo dfu-util -a data -D data.ext4
$ sudo dfu-util -a boot -D boot.vfat
```

**Flashing the bootparts:**

**Note:** With the current hardware configuration using the hardware boot parts is not possible, but this will change in future.

When you want to use the hardware boot partitions of the eMMC you can also flash these via DFU.

Like always you need to set the `dfu_alt_info` first and then activate DFU mode.

```sh
# Set dfu_alt_info
=> setenv dfu_alt_info "tiboot3.bin.raw raw 0x0 0x400 mmcpart 1; tispl.bin.raw raw 0x400 0x1000 mmcpart 1; u-boot.img.raw raw 0x1400 0x2000 mmcpart 1"

# Start DFU mode
=> dfu 0 mmc 0
```

Now you can transfer the binaries like `u-boot.img` via DFU from Host to Target.

```sh
$ sudo dfu-util -a u-boot.img.raw -D u-boot.img
```

After the flashing is done assure that the bootparts are usable by configuring the eMMC.

```sh
# Configure eMMC to use the boot partitions
=> mmc partconf 0 1 1 1
=> mmc bootbus 0 2 0 0
```

**Tips and Tricks:**

1. Erase eMMC

    Erase user data area of eMMC:

    ```sh
    # Select the user data area partition
    => mmc dev 0 0

    # Find out how many blocks the user data area has by trying
    # to read a block from an exceptionaly big address
    => mmc read ${loadaddr} 0x7fffffff 1
    MMC read: dev # 0, block # 2147483647, count 1 ... MMC: block number 0x80000000 exceeds max(0x3b48000)
    0 blocks read: ERROR
    # -> Max block is 0x3b48000

    # Erase all blocks (this can take quite some time)
    => mmc erase 0 0x3b48000
    MMC erase: dev # 0, block # 0, count 62160896 ... 62160896 blocks erased: O
    ```

    Erase boot area partition:

    ```sh
    # Select either primary
    => mmc dev 0 1
    # or secondary boot area partition
    => mmc dev 0 2

    # Find out how many blocks the boot area of the eMMC has by trying
    # to read a block from an exceptionaly big address 
    => mmc read ${loadaddr} 0x7fffffff 1
    MMC read: dev # 0, block # 2147483647, count 1 ... MMC: block number 0x80000000 exceeds max(0xfc00)
    0 blocks read: ERROR
    # -> Max block is 0xfc00

    # Erase all blocks
    => mmc erase 0 0xfc00
    MMC erase: dev # 0, block # 0, count 64512 ... 64512 blocks erased: OK
    ```

    From: [developer.toradex.com - How to erase the eMCC flash memory linux](https://developer.toradex.com/software/linux-resources/linux-features/how-to-erase-the-emmc-flash-memory-linux/)

2. Check if files are existing for troubleshooting

    When you need to validate that certain files are available on the eMMC, which might be handy for troubleshooting you can use the `ext4ls` command to list files.

    The usage is like the following `ext4ls <interface> <device:part> <path>`, so for example:

    ```sh
    => ext4ls mmc 0:2 /lib/firmware
            ./
            ../
            realtek/
            rtl_bt/
            rtl_nic/
            rtlwifi/
            rtw88/
            rtw89/
   147456   rgx.fw.36.53.104.796
   274840   rgx.sh.36.53.104.796
            vision_apps_eaik/
    <SYM>   j722s-c71_0-fw
    <SYM>   j722s-c71_1-fw
    <SYM>   j722s-mcu-r5f0_0-fw
    <SYM>   j722s-main-r5f0_0-fw
            cnm/
            ti-ipc/
    ```

### Boot via Network

Booting via Network can help during development, especially when working on Kernel topics.

To boot via Network you need to re-compile U-Boot with some config changes and prepare some things once on the host side.

Regarding the config changes, please read the section [Build from source for testing](#build-from-source-for-testing).

#### Setup TFTP server

The easiest way to load the Kernel and Device Tree in U-Boot over network is to use a TFTP server.

```sh
# Install dependencies
$ sudo apt update && sudo apt install tftpd-hpa

# Populate shared directory
$ sudo mkdir -p /srv/tftp
$ sudo mkdir -p /srv/tftp/dtb/ti
$ sudo chown -R $USER:$USER /srv/tftp
$ cp Image /srv/tftp/
$ cp k3-am67a-r5-beagley-ai.dtb /srv/tftp/dtb/ti

# Start TFTP server if not yet running
$ sudo systemctl enable --now tftpd-hpa.service 
```

#### Setup NFS server

For making the RFS available for the Target we need also to setup a NFS server. 

```sh
# Install dependencies
$ sudo apt update && sudo apt install nfs-kernel-server

# Populate shared directory
$ sudo mkdir -p /srv/nfs/rootfs
$ sudo chown -R $USER:$USER /srv/nfs
$ sudo tar --same-owner -xzf 46e31456f-dev_20250317_beagley-ai_sdcard_lite_ro_rfs.tar.gz -C /srv/nfs/rootfs/

# Adapt fstab to avoid mounting data partition of eMMC/SD-Card
$ sudo nano /srv/nfs/rootfs/etc/fstab
  # Comment out lines
  #/dev/mmcblk1p1 /boot/u-boot vfat defaults 0 0
  #/dev/mmcblk1p5 /data ext4 defaults 0 0

# Beside the necessary adaptions for proper booting you can now also do other
# adaptions inside /srv/nfs/rootfs/, like adding scripts or eMMC images.

# Configure the share
$ sudo vim /etc/exports
  # Add line
  /srv/nfs/rootfs  *(rw,sync,no_subtree_check,no_root_squash)

# Start NFS server if not yet running
$ sudo systemctl enable --now nfs-kernel-server
```

#### Setup DHCP server

Having a DHCP server running makes network boot more easily because then you don't need to setup the things manually in U-Boot.

When using Network Manager one possible way is to use the CLI tool `nmtui` to setup a NIC in shared mode.

Please set `10.42.0.1` as DHCP IP to avoid any manual setting inside U-Boot later on.

#### Boot process on board

```sh
# Optional steps:
# Override IP of tftp/nfs server (default: 10.42.0.1)
=> setenv serverip 10.42.0.1
# Override path of nfs-rfs (default: /srv/nfs/rootfs)
=> setenv rootpath /srv/nfs/rootfs
# Test if the server is reachable
=> dhcp; ping ${serverip}
ethernet@8000000port@1 Waiting for PHY auto negotiation to complete.... done
link up on port 1, speed 1000, full duplex
BOOTP broadcast 1
BOOTP broadcast 2
DHCP client bound to address 10.42.0.145 (295 ms)
link up on port 1, speed 1000, full duplex
Using ethernet@8000000port@1 device
host 10.42.0.1 is alive

# Switch to network boot (default: mmc)
=> setenv boot network

# Boot board over network
=> run bootcmd_ti_network
...

# Validate that / is an mounted NFS share
root@beagley-ai:~# df -h /
Filesystem                   Size  Used Avail Use% Mounted on
10.42.0.1:/srv/nfs/rootfs  914G  793G   75G  92% /
```

#### NFS troubleshooting

When you have troubles mounting the RFS via NFS you could recompile the Kernel with the option `NFS_DEBUG` enabled to get infos about the possible issue.

With that enabled you can append `nfsrootdebug` to the bootargs like shown below.

```sh
# Example for bootargs with enabled NFS debug mode
=> setenv bootargs console=${console} ${optargs} root=/dev/nfs ip=dhcp nfsroot=${serverip}:${rootpath},nolock,nfsvers=4,timeo=10 rootwait rw rootfstype=nfs rootdelay=3 nfsrootdebug
```

## U-Boot environment

When working with an over-the-air (OTA) update tool like Mender, we need to provide a mechanism through the bootloader that allows us to select the partition to boot from.

With the default U-Boot configuration for TI K3 devices, the `uEnv.txt` file is loaded from the boot partition. This file allows you to adjust the environment and therefore, the boot behavior.

This makes sense for a development setup, where the environment is not persistent and security is not as important as in a production scenario.

For productive use cases, there should be limited or no possibility to change the environment.

See the following commit, in which TI enables the use of `uEnv.txt` for "development workflow".

Link to commit: [git.ti.com - ti-u-boot / commit=949e9cf709391136aa9a7a7f2c2215a9855261ef](https://git.ti.com/cgit/ti-u-boot/ti-u-boot/commit/?h=ti-u-boot-2023.04-next&id=949e9cf709391136aa9a7a7f2c2215a9855261ef)

### Environment setup

U-Boot is configured to store the environment persistent and redundant on the blockdevice at specific offsets.

The offsets and the environment size are defined in the configuration (see `CONFIG_ENV_OFFSET`, `CONFIG_ENV_OFFSET_REDUND` and `CONFIG_ENV_SIZE`).

In userspace there are tools (called `fw_printenv` and `fw_setenv`) that retrieve the information about the environment (offsets and size) via the config file `/etc/fw_env.config`.

Mender uses these two tools to control which partition U-Boot should try to boot from. The most important variables for this are `mender_boot_part` and `upgrade_available`.

The redundancy ensures that if something goes wrong when writing to the environment, U-Boot can revert to the last working state. This is similar to A/B updates.

This can be seen when invoking `saveenv` inside the U-Boot shell or during boot when U-Boot loads the environment.

```sh
# When first env is active
=> saveenv
Saving Environment to MMC... Writing to redundant MMC(1)... OK
# When redundant env is active
=> saveenv
Saving Environment to MMC... Writing to MMC(1)... OK
```

```sh
U-Boot 2026.01-cel (Feb 13 2026 - 07:56:27 +0000)

SoC:   J722S SR1.0 HS-FS
Model: BeagleBoard.org BeagleY-AI
DRAM:  2 GiB (total 4 GiB)
I/TC: Reserved shared memory is enabled
I/TC: Dynamic shared memory is enabled
I/TC: Normal World virtualization support is disabled
I/TC: Asynchronous notifications are disabled
Core:  85 devices, 33 uclasses, devicetree: separate
MMC:   mmc@fa00000: 1
Loading Environment from MMC... Reading from redundant MMC(1)... OK
...
```

You will see the following during the first boot after flashing or completely erasing the environment.

```sh
U-Boot 2026.01-cel (Feb 13 2026 - 07:56:27 +0000)

SoC:   J722S SR1.0 HS-FS
Model: BeagleBoard.org BeagleY-AI
DRAM:  2 GiB (total 4 GiB)
I/TC: Reserved shared memory is enabled
I/TC: Dynamic shared memory is enabled
I/TC: Normal World virtualization support is disabled
I/TC: Asynchronous notifications are disabled
Core:  85 devices, 33 uclasses, devicetree: separate
MMC:   mmc@fa00000: 1
Loading Environment from MMC... *** Warning - bad CRC, using default environment

Reading from MMC(1)...
```

This is because there is no environment yet at that location.
Therefore, U-Boot uses the default environment that is compiled in and writes it to the defined offset before continuing to boot.
This means that you don't need to manually write the environment to the offsets.

### Control environment

With the current setup, all variables can be changed persistently, allowing nearly anything during development. However, for the productive use cases, only Mender relevant variables should be changeable.

Currently, you can make for example the following changes via the environment.   
Note that changes made from userspace (with `fw_setenv`) are always persistent, while changes made from the U-Boot shell (with `setenv`) are only persistent if you save the environment (with `saveenv`) afterwards.

**Enable/Disable readonly setup:**

```sh
# Disable readonly setup
$ sudo fw_setenv boot_rd ""
$ sudo fw_setenv rootmount rw

# Enable readonly setup
$ sudo fw_setenv boot_rd 1
$ sudo fw_setenv rootmount ro
```

**Define which Device Tree Overlays should be loaded:**

Via userspace:

```sh
# Load a single dtbo
$ sudo fw_setenv name_overlays ti/k3-am67a-beagley-ai-csi0-sk5.dtbo
# Don't load any dtbo
$ sudo fw_setenv name_overlays ""
# Load multiple dtbo's
$ sudo fw_setenv name_overlays "ti/k3-am67a-beagley-ai-csi0-sk5.dtbo ti/k3-test.dtbo"
```

Via U-Boot shell:

```sh
=> setenv name_overlays ti/k3-am67a-beagley-ai-csi0-sk5.dtbo
```

**Reset environment:**

The following is only possible within U-Boot shell.

```sh
# Erase the whole environment
=> env erase
Erasing Environment on MMC...
128 blocks erased at 0x1e00: OK
128 blocks erased at 0x1f00: OK
OK

# Reset the whole environment (default env in memory)
=> env default -a
# Reset single variable
=> env default serverip
# Save env after reset
=> saveenv
# Reset board to assure that variables set by SPL are generated again
=> reset
```

**Further recources:**

[docs.u-boot.org - env](https://docs.u-boot.org/en/latest/usage/cmd/env.html)

## Get the latest version

Each image build automatically installs the latest version of the bootloader.

But if you just want to get the latest version of the compiled artifacts, the easiest way is to get them from our APT server.

[apt.balluff.systems - u-boot-balluff-am67a](http://apt.balluff.systems/balluff-linux/pool/main/u/u-boot-balluff-am67a/)

Simply download the `.deb` file to your system and unpack the contents to obtain the binary artifacts.

## Build from source for testing

There are scenarios where you want to test a new feature or you want to adapt the bootloader. To do this, you can build the bootloader from source.

Understanding this process also helps to understand what happens during the package build.

The following steps describe this process.

### Apply the patches

For being license compliance we seperate upstream sources from our changes.   
So each change we did is only visible inside patch files (`debian/patches/`).

That means when you wan't to build manually you first need to apply the patches before beeing able to build the adjusted state.

There are two approaches for applying the patches.

**Apply patches with git-buildpackage:**

For that you need `git-buildpackage` installed.

```sh
# If there is not yet a patch-qeue branch for the working branch
gbp pq import
# If there is already one (but behind remote)
gbp pq rebase
```

**Apply patches with quilt:**

This approach is quite straight forward but only makes sense for a quick test.   
If you planned anyways to do some other changes you want to commit, the approach with git-buildpackage should be prefered.

The patches can technically be applied directly with quilt, but due to the location inside a subdirectory it's recommended to do the following adjustements in init files.   
With these adaptions you can use the alias `uquilt` which then works fine from the root of the repository.

```sh
# vim ~/.bashrc
alias uquilt="quilt --quiltrc=${HOME}/.quiltrc-dpkg"
complete -F _quilt_completion $_quilt_complete_opt uquilt

# ~/.quiltrc-dpkg
d=. ; while [ ! -d $d/debian -a `readlink -e $d` != / ]; do d=$d/..; done
if [ -d $d/debian ] && [ -z $QUILT_PATCHES ]; then
    # falls in Debian-Paketbaum mit ungesetztem $QUILT_PATCHES
    QUILT_PATCHES="debian/patches"
    QUILT_PATCH_OPTS="--reject-format=unified"
    QUILT_DIFF_ARGS="-p ab --no-timestamps --no-index --color=auto"
    QUILT_REFRESH_ARGS="-p ab --no-timestamps --no-index"
    if ! [ -d $d/debian/patches ]; then mkdir $d/debian/patches; fi
fi
```

Source again the `.bashrc`.

```sh
source ~/.bashrc
```

```sh
# Apply all patches
uquilt push -a
# List applied patches
uquilt applied
# List available patches
uquilt series
```

### Get dependencies

There are some dependencies needed to build U-Boot for the Sitara platform.   
The following repositories are required:

```sh
# TI Firmware
git clone https://git.ti.com/git/processor-firmware/ti-linux-firmware.git
cd ti-linux-firmware
git checkout 11.02.11 # or ti-linux-firmware (which should be the latest released version)
cd ..

# OP-TEE
git clone https://github.com/OP-TEE/optee_os.git
cd optee_os
git checkout 4.9.0 # check if there is a newer version
make CROSS_COMPILE=arm-linux-gnueabihf- CROSS_COMPILE64=aarch64-linux-gnu- PLATFORM=k3-am62x CFG_ARM64_core=y
cd ..

# TF-A
git clone https://git.trustedfirmware.org/TF-A/trusted-firmware-a.git
cd trusted-firmware-a
git checkout lts-v2.14.1 # check if there is a newer lts version
make CROSS_COMPILE=aarch64-linux-gnu- ARCH=aarch64 SPD=opteed TARGET_BOARD=lite PLAT=k3
cd ..
```

### Build

**Tipp:** Set the `REPOS_PATH` environment variable to the path where all repositories are cloned to save time when referencing on the different repositories later.

```sh
export REPOS_PATH=<full-path-to-cloned-repos>
```

```sh
# Build r5 image
cd cel-bootloader-sitara/

# Apply one of the defconfigs (beagley-ai or bvs-ma)
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make am67a_beagley_ai_r5_defconfig O=../beagley-ai-build/r5
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make am67a_bvs_ma_r5_defconfig O=../bvs-ma-build/r5
# O= is used to specify the path for out-of-tree build

cd ../beagley-ai-build/r5 # or cd ../bvs-ma-build/r5

ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make menuconfig # in case of config changes
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make savedefconfig # in case of exporting config changes
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- BINMAN_INDIRS=${REPOS_PATH}/ti-linux-firmware/ make -j$(nproc)
cd ../..
```

```sh
# Build a53 image
cd cel-bootloader-sitara/

# Apply one of the defconfigs (beagley-ai or bvs-ma)
ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- make am67a_beagley_ai_a53_defconfig O=../beagley-ai-build/a53
ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- make am67a_bvs_ma_a53_defconfig O=../bvs-ma-build/a53
# O= is used to specify the path for out-of-tree build

# Note: When you want to build U-Boot with Network Boot support you need to activate
# the config fragment network_boot.config like shown below.
ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- make am67a_beagley_ai_a53_defconfig network_boot.config O=../beagley-ai-build/a53

cd ../beagley-ai-build/a53 # or cd ../bvs-ma-build/a53

ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- make menuconfig # in case of config changes
ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- make savedefconfig # in case of exporting config changes
ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- BINMAN_INDIRS=${REPOS_PATH}/ti-linux-firmware/ BL31=${REPOS_PATH}/trusted-firmware-a/build/k3/lite/release/bl31.bin TEE=${REPOS_PATH}/optee_os/out/arm-plat-k3/core/tee-pager_v2.bin make -j$(nproc)
ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- make -j $(nproc) u-boot-initial-env
cd ../..
```

**Note:** `TEE` needs to point to `tee-pager_v2.bin` instead of `tee-raw.bin`. This seems to be an issue inside the J722s specific U-Boot documentation.


```sh
# In case you need to compile some of the tools for testing you can follow the steps below.
cd cel-bootloader-sitara/

# Apply the generic tools-only defconfig
ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- make tools-only_defconfig O=../am67a-build/tools
# O= is used to specify the path for out-of-tree build

cd ../am67a-build/tools

# Compile fw_printenv
# NOTE: This variant differs heavily from the libubootenv variant which is used by default.
ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- HOSTLDFLAGS="-static" make -j $(nproc) envtools

# Compile generic u-boot-tools (e.g. mkimage)
ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- make tools-only
```

You should now see at least the following artifacts in the build directories.

```sh
tree -L 1 beagley-ai-build/r5
beagley-ai-build/r5
├── arch
...
├── tiboot3-j722s-hs-evm.bin
├── tiboot3-j722s-hs-evm.bin.map
├── tiboot3-j722s-hs-fs-evm.bin
├── tiboot3-j722s-hs-fs-evm.bin.map
├── tiboot3.bin -> ./tiboot3-j722s-hs-fs-evm.bin
...

19 directories, 60 files


tree -L 1 beagley-ai-build/a53/
beagley-ai-build/a53/
├── arch
...
├── tispl.bin
...
├── u-boot.img
...

19 directories, 61 files
```

In the case of the latest EVM board the artifact `tiboot3-j722s-hs-fs-evm.bin` is used as MLO, `tispl.bin` as SPL  and `u-boot.img` as bootloader image.

### Deploy

Depending on the boot source the deployment can differ a bit.

**Note:** Independently of the targeted boot source please assure to reset the environment to the default state, in case you did some changes to the environment before. This is described in the section [Control environment](#control-environment).

#### SD-Card Boot

To use the artifacts, you can copy them to the boot partition of the prepared SD card.

```sh
cp beagley-ai-build/r5/tiboot3-j722s_hs-fs-evm.bin /media/<username>/boot/tiboot3.bin # ROM code will only load tiboot3.bin so please make sure you rename it properly
cp beagley-ai-build/a53/tispl.bin /media/<username>/boot/
cp beagley-ai-build/a53/u-boot.img /media/<username>/boot/

sync /media/<username>/boot/

sudo umount /media/<username>/boot/
sudo umount /media/<username>/rfsa/
sudo umount /media/<username>/data/
```

#### eMMC Boot

For eMMC the deployment depends on the current state of your target system. In case you have already a working system you could transfer the files via SSH or DFU (in U-Boot) to the target and replace the existing files on the eMMC.

When transfering the files via SSH you can simply copy the files to the boot partition of the eMMC and then reboot the system. So no real difference to the process with the SD-Card. After switching to booting via the hardware boot partitions the process is a bit different and will be described here after the switch is done.

When you don't have a working system, you can use DFU to boot the system into the U-Boot shell, where you can then transfer the files to the eMMC. The process of booting via DFU and transferring files is described in the section [Boot via DFU](#boot-via-dfu).

## Build and package for deployment

If you want to adapt to the bootloader and publish the changes, you can follow the steps below.

1. Clone the `cel-bootloader-sitara` repository, if not already done, and checkout the debian working branch for the device.

   For the AM67A it is the branch called: `debian/am67a`

2. Create a feature branch based on the debian branch you checkout before.

   For changes inside `debian/` you can directly do and commit the changes.
   In such a case you can skip the steps 3-7.
   If you need to adapt files from upstream please follow the next step to keep upstream
   sources clean, so that we are easily license compliant.
   Upstream is basically anything outside the debian folder.

3. Import the existing patches into a `patch-queue` branch via git-buildpackage.

   ```sh
   gbp pq import

   # If you had already a local patch-queue branch you either remove this local branch first
   # or rebase the patch commits with the following command
   gbp pq rebase
   ```

4. Make the necessary adaptions of the upstream sources.

5. If necessary, do a manual testbuild to check that everything is working as expected.

   This is described in an earlier section.

6. Commit your changes as normal, but note the following points which are important for the patch handling:

   - include only changes of the upstream sources
   - include only one logical change per commit
   - make the commit description as meaningful as possible
     (ideally add a detailed description and not just a short message)

7. Export the commits as patches with git-buildpackage

   When you are done with changing upstream files you can use git-buildpackage to simply export each created
   commit as patch file. The resulting patchfile contains the commit message, so no info is lost.

   ```sh
   gbp pq export
   ```

   You should now be back inside your created feature branch and should see created files inside `debian/patches/`

8. Update the Debian changelog.

    ```sh
    dch -i -D stable

    # You can also use the "gbp dch" command to create on a automatic way infos about the changes.
    # For that you need to follow a more strict workflow.
    # The current recommendation is to use "dch" directly.
    ```

    It is important to distinguish between an upgrade to a higher upstream version and changes inside the upstream sources.   
    Depending on the change, you should update the upstream version number (and later create a new upstream tarball) or a new patch level.   
    The change description should ideally include the reference to the newly applied and removed patches, and perhaps a short description of the changes.

9. Check-in and commit the files inside `debian/`

    ```sh
    git add debian/
    git commit # Message can be something like "Bump Debian package version to 2024.04-3"
    ```

10. Tag the last commit with the Debian package version and push it to origin.

    ```sh
    git tag debian/am67a/2026.01-2
    git push origin debian/am67a/2026.01-2
    ```

11. Create upstream tarball using `git archive`

    ```sh
    mkdir ../tarballs

    git archive --prefix <package-name>_<version>/ -o ../tarballs/<package-name>_<version>.orig.tar.gz <upstream-tag>/<upstream-banch>

    # example
    git archive --prefix u-boot-balluff-am67a_2026.01/ -o ../tarballs/u-boot-balluff-am67a_2026.01.orig.tar.gz v2026.01


    # or via gbp buildpackage where version is automatically detected by changelog file
    gbp export-orig --tarball-dir=../tarballs
    ```

12. Build the package with git-buildpackage or directly with dpkg-buildpackage

    ```sh
    CC=aarch64-linux-gnu- gbp buildpackage --git-arch=arm64 --git-builder=dpkg-buildpackage --no-sign --host-arch arm64 --post-clean

    # If you want to build for testing and you have uncommited changes use the --git-ignore-new option
    CC=aarch64-linux-gnu- gbp buildpackage --git-ignore-new --git-arch=arm64 --git-builder=dpkg-buildpackage --no-sign --host-arch arm64 --post-clean

    # When you build on a debian branch which is named differently than the default one you need to override this with the --git-debian-branch option
    CC=aarch64-linux-gnu- gbp buildpackage --git-debian-branch=feature/189900-support_evm_and_demo_board --git-builder=dpkg-buildpackage --git-arch=arm64 --no-sign --host-arch arm64 --post-clean

    # If your are not inside the debian working branch you may need to override this
    # with the --git-debian-branch options.
    ```

    If you get problems due to some issue regarding the source package, please check first if
    for the used version in `debian/changelog` a upstream git tag exists.
    Otherwise gbp buildpackage will fail creating the upstream tarball.

13. Get the new artifacts for integration into our APT server.

    ```sh
    ls -l ../builds/
    total 1952
    -rw-rw-r-- 1 1000 1000   7321 Mär  2 10:58 u-boot-balluff-am67a_2026.01-2_arm64.buildinfo
    -rw-rw-r-- 1 1000 1000   3209 Mär  2 10:58 u-boot-balluff-am67a_2026.01-2_arm64.changes
    -rw-rw-r-- 1 1000 1000  32168 Mär  2 10:54 u-boot-balluff-am67a_2026.01-1.debian.tar.xz
    -rw-rw-r-- 1 1000 1000   1082 Mär  2 10:54 u-boot-balluff-am67a_2026.01-1.dsc
    lrwxrwxrwx 1 1000 1000     96 Mär  2 10:54 u-boot-balluff-am67a_2026.01.orig.tar.gz -> ../tarballs/u-boot-balluff-am67a_2026.01.orig.tar.gz
    -rw-r--r-- 1 1000 1000 975736 Mär  2 10:58 u-boot-image-balluff-am67a-beagley-ai_2026.01-2_arm64.deb
    -rw-r--r-- 1 1000 1000 975736 Mär  2 10:58 u-boot-image-balluff-am67a-bvs-ma_2026.01-2_arm64.deb
    ```

## Upgrade to new upstream release

If there is a new upstream release of U-Boot and we want to upgrade to that version we can use the following steps.

```sh
# Clone the cel-bootloader-sitara repository if not already done
git clone git@ssh.dev.azure.com:v3/BalluffAzDevOpsProd/Common%20Embedded%20Linux/cel-bootloader-sitara

# Catch the latest changes in our repository
git pull

# Now check if you have already added the upstream U-Boot tree
git remote -v
origin	git@ssh.dev.azure.com:v3/BalluffAzDevOpsProd/Common%20Embedded%20Linux/cel-bootloader-sitara (fetch)
origin	git@ssh.dev.azure.com:v3/BalluffAzDevOpsProd/Common%20Embedded%20Linux/cel-bootloader-sitara (push)
upstream	https://source.denx.de/u-boot/u-boot.git (fetch)
upstream	https://source.denx.de/u-boot/u-boot.git (push)

# If the two line starting with "upstream" are missing you can add the upstream repository with the following command
git remote add --mirror=fetch upstream https://source.denx.de/u-boot/u-boot.git

# Fetch the changes from the upstream U-Boot tree
git fetch upstream --tags

# Push the relevant tag(s) to our repository
git push origin v2026.01

# Now you can merge the state you want to have into your working branch
# For example:
git checkout debian/am67a
git checkout -b feature/xyz-update_to_v2026.01
git merge v2026.01

# Try to apply the existing patches
# ... when no patch-qeue branch exist
gbp pq import
# ... when patch-qeue branch already exist
gbp pq rebase

# It's quite common that you need to fix/adapt some patches.
# When this is neccesarry the --time-machine=NUM option can help
# See section from man page
#	"When importing a patch queue fails, go back commit-by-commit on the current branch to check if the patch-queue applies there. Do this at most NUM times. This can be useful if the patch-queue doesn't apply to the current branch HEAD anymore, e.g. after importing a new upstream version."
```

## Important adaptions

All adaptions of the sources are reflected by patch files which you can find inside `debian/patches/*`.

- Adjust BeagleY-AI config for CEL
  |--> `configs/am67a_beagley_ai_r5_defconfig`   
  |--> `configs/am67a_beagley_ai_a53_defconfig`   

- Added BVS-MA config for CEL   
  |--> `configs/am67a_bvs_ma_r5_defconfig`   
  |--> `configs/am67a_bvs_ma_a53_defconfig`   

- Added BVS-MA board files   
  |--> `board/balluff/bvs-ma/`   
  |--> `include/configs/bvs_ma.h`   
  |--> `arch/arm/mach-k3/j722s/Kconfig`   

- Extend environment for network dev boot (conditionally used depending on config fragment)   
  |--> `board/beagle/beagley-ai/beagley-ai.env`   
  |--> `include/env/ti/network.env`   
  |--> `configs/network_boot.config`

- Adapt environment for initramfs boot (when booting from mmc)   
  |--> `include/env/ti/mmc.env`   
  |--> `include/env/ti/ti_common.env`   
  |--> `include/env_mender.h `   

- Adapt environment and boot logic for Mender support   
  |--> `include/config_mender.h`   
  |--> `include/env_mender.h `   
  |--> `include/env_default.h`   
  |--> `scripts/Makefile.autoconf`   
  |--> `include/config_mender_defines.h`   
  |--> `include/env/mender/beagley-ai.h`   
  |--> `include/env/mender/bvs-ma.h`   
  |--> `configs/am67a_beagley_ai_r5_defconfig`   
  |--> `configs/am67a_beagley_ai_a53_defconfig`   
  |--> `configs/am67a_bvs_ma_r5_defconfig`   
  |--> `configs/am67a_bvs_ma_a53_defconfig`   

  Overview about adaptions introduced by Mender support:

  ![mender-bootlogic-adaptions.png](./assets/mender-bootlogic-adaptions.png)

## Known issues

- Package upgrades on device doesn't work

  If you try to upgrade the package on the target device using `apt`, you will get error messages like the following.

  ```
  unable to make backup link of './boot/u-boot/tiboot3.bin' before installing new version: Operation not permitted
  ```

  This is simply because `/boot/u-boot` is a directory on the `boot` partition which has a `vfat` filesystem.

  The package manager tries to make a backup of the files, before installing the new version, but this is not possible on a `vfat` filesystem. So it refuses to upgrade the package at all.

  If you need to upgrade the package on the device, which may be the case if you want to test the package manually, you can use the workaround described below.

  ```sh
  # After executing the following command you should assure that the device is not rebooted until the new package ist installed
  apt purge u-boot-image-balluff-am67a-beagley-ai
  apt update
  apt install u-boot-image-balluff-am67a-beagley-ai
  reboot
  ```

  This is not a real problem for CEL, because we provide updates via Mender and not via `apt`.

  One possible solution would be to install the artifacts not directly in `/boot/u-boot` but in another directory on the rfs partition such as `/boot` or `/usr`.

  Then we would need a copy command in the `postinst` script to copy the files to the boot partition and a remove command in the `postrm` script to clean up the boot partition.

  The downside of this solution is that we need twice as much disk space for the U-Boot artifacts.


## Resources

- [docs.u-boot.org TI/K3](https://docs.u-boot.org/en/latest/board/ti/k3.html)
- [docs.u-boot.org - TI/J722s EVM](https://docs.u-boot.org/en/latest/board/ti/j722s_evm.html)
- [docs.u-boot.org - Environment](https://docs.u-boot.org/en/latest/usage/environment.html)
- [docs.u-boot.org - Bootstd](https://docs.u-boot.org/en/stable/develop/bootstd.html)
- [hub.digi.com - U-Boot reference-manual](https://hub.digi.com/dp/path=/support/asset/u-boot-reference-manual/)
- [trustedfirmware-a.readthedocs.io - Initial Build](https://trustedfirmware-a.readthedocs.io/en/latest/getting_started/initial-build.html)
- [software-dl.ti.com - OPTEE](https://software-dl.ti.com/processor-sdk-linux/esd/AM64X/09_00_00_03/exports/docs/linux/Foundational_Components_OPTEE.html)
- [software-dl.ti.com - ATF](https://software-dl.ti.com/processor-sdk-linux/esd/AM64X/09_00_00_03/exports/docs/linux/Foundational_Components_ATF.html)
- [software-dl.ti.com - Migration_Guide](https://software-dl.ti.com/processor-sdk-linux/esd/AM64X/09_00_00_03/exports/docs/linux/Foundational_Components_Migration_Guide.html)
- [dev.ti.com - Bootflow](https://dev.ti.com/tirex/explore/content/am64x_academy_9_00_00_00_v2/_build_am64x_academy_9_00_00_00_v2/source/linux/ch-develop/dev-bootflow.html)
- [dev.ti.com - Build U-Boot](https://dev.ti.com/tirex/explore/content/am64x_academy_9_00_00_00_v2/_build_am64x_academy_9_00_00_00_v2/source/linux/ch-develop/dev-build-uboot.html)
