U-Boot on the BeagleBone Black

The AM335X contains ROM code that can load a bootloader from external memory such as the on-board eMMC. On reset, this ROM code searches for the bootloader and then copies it to the internal RAM before executing it. The internal RAM on the AM335X is 128KB, but due to various limitations, only 109KB is available for the initial bootloader for program memory, heap and stack.

A fully featured version of U-Boot can be over 400KB, hence it is not possible to load this immediately. For this reason, a cut down version of U-Boot called U-Boot SPL (Second Program Loader) is loaded first, and once it has initialised the CPU, it chain loads a fully featured version of U-Boot (u-boot.img).

By default, the ROM code in the Sitara AM3359 will boot from the MMC1 interface first (the onboard eMMC), followed by MMC0 (external uSD), UART0 and USB0.

If the boot switch (S2) is held down during power-up, the ROM will boot from the SPI0 Interface first, followed by MMC0 (external uSD), USB0 and UART0. This allows the BeagleBone Black to bypass the onboard eMMC and boot from the removable uSD (provided no valid boot device is found on SPI0.) This can be used to recover from a corrupted onboard eMMC/U-Boot.

The ROM code will try to load and execute the first stage bootloader called "MLO" (U-Boot SPL) from a Fat 12/16 or 32 bit MBR based filesystem. Alternatively, if using eMMC, the bootloader can be loaded using RAW mode.

Early BeagleBone Black images included MLO (and u-boot.img) on a FAT file-system in the root directory of the active primary partition. The card would contain two partitions, a FAT32 partition and Linux ext3/4. The full/second stage U-Boot would contain hard-coded environment variables to load uEnv.txt as the environment variables couldn't be saved using saveenv.

In more recent times, the RAW mode has been adopted. In RAW mode, the ROM code will search sector #0 (offset 0x00000), sector #256 (0x20000), sector #512 (0x40000) and sector #768 (0x60000) for a TOC structure/Configuration Header.

If you still want to use the MMC as a disk, then it must have a Master Boot Record (MBR) containing one or more partition table entries at 0x00000. This prevents placing U-Boot SPL/MLO there. As the ROM Code will search four sectors, it makes sense to place U-Boot SPL/MLO at 0x20000 (sector #256).

U-boot SPL can then load the fully featured U-Boot second stage bootloader at 0x60000. One of the advantages of running U-Boot from block memory is the environment variables can be stored. This method means you can now modify and save the environment variables, negating the need to load uEnv.txt.

Don't panic that you are encroaching into partitions on the disk. Most primary partitions will start at sector 2048, and with 512B per sector you have an entire 1MB to play with:

Disk /dev/sdb: 7.4 GiB, 7948206080 bytes, 15523840 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000

Device     Boot  Start     End Sectors  Size Id Type
/dev/sdb1  *      2048  198655  196608   96M  e W95 FAT16 (LBA)
/dev/sdb2       198656 7577599 7378944  3.5G 83 Linux

Compiling U-Boot for the BeagleBone Black

Download the latest version of U-Boot, extract the files to a working folder and patch:

wget ftp://ftp.denx.de/pub/u-boot/u-boot-latest.tar.bz2
tar -xjf u-boot-latest.tar.bz2
cd u-boot-2015.10
wget https://rcn-ee.com/repos/git/u-boot-patches/v2015.10/0001-am335x_evm-uEnv.txt-bootz-n-fixes.patch
patch -p1 < 0001-am335x_evm-uEnv.txt-bootz-n-fixes.patch

Now cross compile U-Boot using the am335x BeagleBone Black configuration:

make CROSS_COMPILE=arm-linux-gnueabihf- distclean
make CROSS_COMPILE=arm-linux-gnueabihf- am335x_boneblack_config
make CROSS_COMPILE=arm-linux-gnueabihf-

Compiling should conclude with output similar to below showing that an image of MLO - the first stage uBoot Bootloader is generated.

  LD      spl/u-boot-spl
  OBJCOPY spl/u-boot-spl.bin
  MKIMAGE MLO
  CFG     spl/u-boot-spl.cfg

Scrolling up the output messages, you should find where the second stage U-Boot bootloader (u-boot.img) is generated.

  LDS     u-boot.lds
  LD      u-boot
  OBJCOPY u-boot.bin
  MKIMAGE u-boot.img
  OBJCOPY u-boot.srec
  CFG     u-boot.cfg

Testing the fully featured second stage U-Boot Bootloader

If desired, you can test the second stage bootloader by loading it to RAM:

setenv ipaddr 192.168.0.250
setenv serverip 192.168.0.251
tftp 0x80800000 u-boot.bin
go 0x80800000

Saving the Bootloader Images to Flash/Card

Copy the files to the start of the uSD Card (/dev/sdb) or MMC Flash (/dev/mmcblk0):

dd if=MLO of=/dev/mmcblk0 bs=512 seek=256 count=256 conv=notrunc
dd if=u-boot.img of=/dev/mmcblk0 bs=512 seek=768 count=1024 conv=notrunc

If you are writing to removable uSD Card, you can finish by flushing the cache with:

sudo blockdev --flushbufs /dev/sdb

Setting Environment Variables for booting from TFTP/NFSRoot

As you can now modify & save the environment variables within U-Boot, this is an example of my setup loading an image from TFTP and booting to a NFS root filesystem.

Run the following commands within U-Boot:

setenv fdtaddr 0x80F80000
setenv loadaddr 0x80007fc0
setenv ipaddr 192.168.0.250
setenv serverip 192.168.0.251
setenv loadfdt 'tftpboot ${fdtaddr} am335x-boneblack.dtb'
setenv loadimage 'tftpboot ${loadaddr} uImage-BBB'
setenv bootargs 'console=ttyO0,115200n8 root=/dev/nfs rw nfsroot=192.168.0.251:/home/cpeacock/export/rootfs ip=192.168.0.250:::::eth0'
setenv bootcmd 'run loadimage; run loadfdt; bootm ${loadaddr} - ${fdtaddr}'

Save your changes:

saveenv

Booting from uSD Card (MMC0)

Holding down S2 enables booting from the MMC0 interface (uSD Card). If you wish to always boot from the uSD Card, you can erase the contents of the on-board MMC. The ROM code will attempt to load a bootloader from the on-board MMC and fail over to the next interface, the uSD Card.

From U-Boot, erase the contents of the on-board MMC by executing:

U-Boot# mmc erase 0 1000
MMC erase: dev # 1, block # 0, count 4096 ... 4096 blocks erased: OK

Setting up the environment area on your uSD Card

By default, U-Boot will attempt to load/store environment variables from MMC Device 1 (On-board MMC), Part 2.

This is specified in include/configs/am335x_evm.h:

#elif defined(CONFIG_EMMC_BOOT)
#undef CONFIG_ENV_IS_NOWHERE
#define CONFIG_ENV_IS_IN_MMC
#define CONFIG_SPL_ENV_SUPPORT
<B>#define CONFIG_SYS_MMC_ENV_DEV		1
#define CONFIG_SYS_MMC_ENV_PART		2</B>
#define CONFIG_ENV_OFFSET		0x0
#define CONFIG_ENV_OFFSET_REDUND	(CONFIG_ENV_OFFSET + CONFIG_ENV_SIZE)
#define CONFIG_SYS_REDUNDAND_ENVIRONMENT
#endif

If you move your uSD Card between BeagleBones, it may be advantageous to store the environment area on the uSD card.

To enable this, modify the above block of defines found in include/configs/am335x_evm.h to:

#elif defined(CONFIG_EMMC_BOOT)
#undef CONFIG_ENV_IS_NOWHERE
#define CONFIG_ENV_IS_IN_MMC
#define CONFIG_SPL_ENV_SUPPORT
#define CONFIG_SYS_MMC_ENV_DEV		0
#define CONFIG_ENV_OFFSET		0xE0000
#endif

and recompile.