A Handy U-Boot Trick

Listing 4. Well Formatted Content of the Variable bootcmd

01 gpio set 53;
02 i2c mw 0x24 1 0x3e;
03 run findfdt;
04 mmc dev 0;
05 if mmc rescan ;
06 then
07     echo micro SD card found;
08     setenv mmcdev 0;
09 else
10     echo No micro SD card found, setting mmcdev to 1;
11     setenv mmcdev 1;
12 fi;
13 setenv bootpart ${mmcdev}:2;
14 mmc dev ${mmcdev};
15 if mmc rescan;
16 then
17     gpio set 54;
18     echo SD/MMC found on device ${mmcdev};
19     if run loadbootenv;
20     then
21         echo Loaded environment from ${bootenv};
22         run importbootenv;
23     fi;
24     if test -n $uenvcmd;
25     then
26         echo Running uenvcmd ...;
27         run uenvcmd;
28     fi;
29     gpio set 55;
30     if run loaduimage;
31     then
32         gpio set 56;
33         run loadfdt;
34         run mmcboot;
35     fi;
36 fi;

For better insight into the workings of U-Boot, I recommend interrupting the execution and dropping to the U-Boot shell. At the shell, you can see a list of supported commands by typing help or ?. You can list all defined environment variables with the env print command. These environment variables are a powerful tool for scripting. To resume the boot sequence, you either can issue the boot command or run bootcmd. A good way to understand what the bootcmd is doing is to execute each command one at a time from the U-Boot shell and see its effect. You may replace the if...then...else...fi blocks by executing the conditional statement without the if part and checking its output by typing echo $?.


The DHCP (Dynamic Host Configuration Protocol) is a protocol to provide hosts with the necessary information to access the network on demand. This includes the IP address for the host, the DNS servers, the gateway server, the time servers, the TFTP server and so on. The DHCP server also can provide the name of the file containing the kernel image that the host must get from the TFTP server to continue booting. The DHCP server can be set up to provide a configuration either for the entire network or on a per-host basis. Configuring the filename (Listing 5) for the entire network is not a good idea, as one kernel image or ELF file will execute only on the architecture for which it was built. For instance, the vmlinuz image built for an x86_64 will not work on a system with an ARM-based processor.

Listing 5. The Host Configuration Section for a DHCP Server

subnet netmask {
    option domain-name-servers;
    option routers;

        # The BeagleBone Black 1
    host BBB-1 {
        filename "/BI/uImage";
        hardware ethernet C8:A0:30:B0:88:EB;

Important Note:

Be extremely careful while using the DHCP server. A network must not have more than a single DHCP server. A second DHCP server will cause serious problems on the network. Other users will lose network access. If you are on a corporate or a university network, you will generate a high-priority incident inviting the IT department to come looking for you.

The Ubuntu apt repository offers two DHCP servers: isc-dhcp-server and dhcpcd. I prefer to use isc-dhcp-server.

The isc-dhcpd-server from the Ubuntu repository is pretty advanced and implements all the necessary features. I recommend using Webmin to configure it. Webmin is a Web-based configuration tool that supports configuring several Linux-based services and dæmons. I recommend installing Webmin from the apt repository. See the Webmin documentation for instructions for adding the Webmin apt repository to Ubuntu.

Once you have your DHCP server installed, you need to configure a subnet and select a pool of IP addresses to be dished out to hosts on the network on request. After this, add the lines corresponding to the host from Listing 5 into your /etc/dhcp/dhcpcd.conf file, or do the equivalent from Webmin's intuitive interface. In Listing 5, C8:A0:30:B0:88:EB corresponds to the BeagleBone's Ethernet address. The next-server is the address of the TFTP server from which to fetch the kernel image of ELF. The /BI/uImage filename is the name of the kernel image. Rename the image to whatever you use.


TFTP (Trivial File Transfer Protocol) is a lightweight file-transfer protocol. It does not support authentication methods. Anyone can connect and download any file by name from the server or upload any file to the server. You can, however, protect your server to some extent by setting firewall rules to deny IP addresses out of a particular range. You also can make the TFTP home directory read-only to the world. This should prevent any malicious uploads to the server. The Ubuntu apt repository has two different TFTP servers: atftp and tftp-hpa. I recommend tftp-hpa, as development of atftp has seized since 2004.

tftpd-hpa is more or less ready to run just after installation. The default file store is usually /var/lib/tftpboot/, and the configuration files for tftp-can may be found in /etc/default/tftpd-hpa. You can change the location of the default file store to any other location of your choice by changing the TFTP_DIRECTORY option. The TFTP installation creates a user and a group called tftp. The tftp server runs as this user. I recommend adding yourself to the tftp group and changing permissions on the tftp data directory to 775. This will let you read and write to the tftp data directory without switching to root each time. Moreover, if files in the tftp data directory are owned by root, the tftp server will not be able to read and serve them over the network. You can test your server by placing a file there and attempting to get it using the tftp client:

$ tftp -c get uImage[COMMAND]

Some common problems you may face include errors due to permission. Make sure that the files are readable by the tftp user or whichever user the tftpd runs as. Additionally, directories must have execute permission, or tftp will not be able to descend and read the content of that directory, and you'll see a "Permission denied" error when you attempt to get the file.

U-Boot Scripting

Now that you have your DHCP and TFTP servers working, let's write a U-Boot script that will fetch the kernel image and boot it. I'm going to present two ways of doing this: using DHCP and using only TFTP. As I mentioned before, running a poorly configured DHCP server will cause a network-wide disruption of services. However, if you know what you are doing and have prior experience with setting up network services, this is the simplest way to boot the board.

A DHCP boot can be initiated simply by adding or modifying the uenvcmd variable in the uEnv.txt file, as shown in Listing 6. uEnv.txt is found in the FAT32 partition of the BeagleBone Black. This partition is available to be mounted when the BeagleBone Black is connected to your computer via USB cable.

Listing 6. An Example of the uenvcmd Variable for DHCP Booting

echo Booting the BeagleBone Black from LAN (DHCP)...
dhcp ${kloadaddr}
tftpboot ${fdtaddr} /BI/${fdtfile}
setenv bootargs console=${console} ${optargs} root=${mmcroot}
 ↪rootfstype=${mmcrootfstype} optargs=quiet
bootm ${kloadaddr} - ${fdtaddr}

For a TFTP-only boot, you manually specify an IP address for the development board and the TFTP server. This is a much safer process, and you incur very little risk of interfering with other users on the network. As in the case of configuring to boot with DHCP, you must modify the uenvcmd variable in the uEnv.txt file. The script shown in Listing 7 is an example of how to set up your BeagleBone Black to get a kernel image from the TFTP server and pass on the execution to it.

Listing 7. An Example of uenvcmd Variable for TFTP Booting

echo Booting the BeagleBone Black from LAN (TFTP)...
env set ipaddr
env set serverip
tftpboot ${kloadaddr} /BI/${bootfile}
tftpboot ${fdtaddr} /BI/${fdtfile}
setenv bootargs console=${console} ${optargs} root=${mmcroot}
 ↪rootfstype=${mmcrootfstype} optargs=quiet
bootm ${kloadaddr} - ${fdtaddr}

Both Listing 6 and 7 are formatted to give a clear understanding of the process. The actual uEnv.txt file should look something like the script shown in Listing 8. For more information about U-Boot scripting, refer to the U-Boot FAQ and U-Boot Manual. The various commands in the uenvcmd variable must be on the same line separated by a semicolon. You may notice that I place my script in uenvcmdx instead of uenvcmd. This is because test -n throws an error to the console based on the content of the variable it is testing. Certain variable contents, especially long complicated scripts, cause the test -n to fail with an error message to the console. Therefore, I put a simple command to run uenvcmdx in uenvcmd. If you find that your script from the uEnv.txt is not being executed, look for an error on the serial console like this:

test - minimal test like /bin/sh

test [args..]

Listing 8. An Example of uEnv.txt for TFTP Booting

uenvcmdx=echo Booting the bone from emmc...; env set ipaddr 
 ↪; env set serverip; tftpboot 
 ↪${kloadaddr} /BI/${bootfile}; tftpboot ${fdtaddr} 
 ↪/BI/${fdtfile}; setenv bootargs console=${console} 
 ↪${optargs} root=${mmcroot} rootfstype=${mmcrootfstype} 
 ↪optargs=quiet; bootm ${kloadaddr} - ${fdtaddr}
uenvcmd=run uenvcmdx

On some development boards like the BeagleBoard xM, the Ethernet port is implemented on the USB bus. Therefore, it is necessary to start the USB subsystem before attempting any network-based boot. If your development board does not hold a Flash memory on board, it may not have a MAC address either. In this case, you will have to set a MAC address before you can issue any network requests. You can do that by setting the environment variable ethaddr along with the rest of the uEnv.txt script.

An alternative but cumbersome way to change the default boot sequence is to modify the U-Boot source code. Modifying the source code gives you greater versatility for booting your development board. When you interrupt the U-Boot boot sequence, drop to the U-Boot shell and issue the env print command, you'll see a lot of environment variables that are defined by default. These environment variables are defined as macros in the source code. Modifying the source code aims at modifying these variables. As shown in Figure 1, U-Boot begins loading the kernel by executing the script in bootcmd. Hence, this is the variable that must be modified.

To begin, you'll need the source code to U-Boot from the git repository:

$ git clone git://git.denx.de/u-boot.git

Before making any modifications, I recommend compiling the unmodified source code as a sanity check:

$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean

$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- am335x_evm_config

$ make -j 8 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

This most likely will work without a hitch. Now you can modify the u-Boot/include/configs/am335x_evm.h file. In this file, you'll find code similar to Listing 9. Modify this as you please and re-compile. Depending on your target board, you will have to modify a different file. The files to some common target platforms are:

  • Panda Board: u-Boot/include/configs/omap4_common.h

  • BeagleBoard: u-Boot/include/configs/omap3_beagle.h

Listing 9. Part of the u-Boot/include/configs/am335x_evm.h File Responsible for the Default Script in the bootcmd Variable

  "mmc dev ${mmcdev}; if mmc rescan; then " \
    "echo SD/MMC found on device ${mmcdev};" \
    "if run loadbootenv; then " \
      "echo Loaded environment from ${bootenv};" \
      "run importbootenv;" \
    "fi;" \
    "if test -n $uenvcmd; then " \
      "echo Running uenvcmd ...;" \
      "run uenvcmd;" \
    "fi;" \
    "if run loaduimage; then " \
      "run mmcboot;" \
    "fi;" \
  "fi;" \


I hope the instructions provided here help you create a system to develop and deploy bare-metal programs and kernel images quickly. You also may want to look into u-boot-v2, also known as Barebox. The most helpful code modification that I suggest here is to compile the U-Boot with an elaborate boot sequence that you can tailor to your needs with the least modifications. You can try out some fancy scripts to check and update firmware over LAN—I would consider that really cool. Write to me at bharath (you-know-what) lohray (you-know-what) com.


Bharath Bhushan Lohray is a PhD student working on his dissertation on image compression techniques at the Department of Electrical and Computer Engineering, Texas Tech University.


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

One of the more frustrating

sollen's picture

One of the more frustrating things about making repairs to your car is when they don’t go quite correctly, and the car fails 30W LED working lamp For Buses the first post-repair test drive. Any time you replace parts, you need to put the new parts in so that they fit and function the way your car came from the factory.

Normal Procedure but with DHCP

kasiviswanathan's picture

This is the general method used during kernel development.

In case of boot loader development, is there any possibility of doing the same. AFAIK there is no technique to circumvent it.

Very nice write up. I too

Mark Romero's picture

Very nice write up. I too have found that netbooting has made developing on the Beaglebone much easier. It also eliminates the possibility of corrupting cards by performing too many writes as can be the case when running the mkcard script frequently on the same card (ask me how I know this can happen.)

In addition to your process, I also use a NFS mount for the rootfs. This makes is so that I can apply a full image without even touching the SD card. The only issue I ran into was that connection manager will hijack eth0 and drop the NFS connection, so it has to be turned off.

U-boot (at least the Angstrom build) makes netbooting to an NFS root very easy with a built-in netboot command. All you have to do is set the server ip, and if you would like the NFS mount path and it will obtain a DHCP, load the uImage and device tree, and mount the NFS rootfs. Here is my uEnv.txt

uenvcmd=run netboot

On my NFS server the rootfs archive is extracted to /export/rootfs

Again great write up and thanks for sharing.