As stated at the beginning of this chapter, using the serial console, we can get access to the bootloader.
Actually, all the developer kits presented in this book have two bootloaders: a pre-bootloader or Secondary Program Loader (SPL), named MLO for the BeagleBone Black, boot.bin for SAMA5D3 Xplained, and SPL for the Wandboard, which initializes the hardware components, such as the RAM and some mass storage devices, and bootloader named U-Boot for all boards, which is the real bootloader that initializes almost all the peripherals and has support for, among other things, booting over network and a scriptable shell through which basic commands can be given. Now the one million dollar question is: why should a developer be able to manage the bootloader too?
Well the answers are more than one; however, the most important ones are:
By passing a well-formed command line to the kernel, we can change some functionalities in the running filesystem.
From the bootloader, we can easily manage a factory restore method (it is usually made with a hidden button in a tiny hole on the system's box. By keeping that button pressed while powering up the system, the user can cause the whole system to reset to its factory defaults).
Through the bootloader, we can decide which device to use to perform a boot. For instance, we can force a boot from a microSD or from a USB key.
So now let's see how we can get the U-Boot's prompt using one kit since they're running the same U-Boot version (the following messages are from SAMA5D3 Xplained).
Just after the power up, we will see some interesting messages on the serial console:
RomBOOTU-Boot SPL 2016.03-dirty (Apr 15 2016 - 19:51:18)Trying to boot from MMCreading u-boot.imgU-Boot 2016.03-dirty (Apr 15 2016 - 19:51:18 +0200)CPU: SAMA5D36Crystal frequency: 12 MHzCPU clock : 528 MHzMaster clock : 132 MHzDRAM: 256 MiBNAND: 256 MiBMMC: mci: 0reading uboot.env** Unable to read "uboot.env" from mmc0:1 **Using default environmentIn: serialOut: serialErr: serialNet: gmac0Error: gmac0 address not set., macb0Error: macb0 address not set.Hit any key to stop autoboot: 1
At this time, we have less than 1 second to strike the Enter key to stop the countdown and get the U-Boot prompt shown as follows:
=>
Well, now we can get a list of the available commands using the help command:
=> help? - alias for 'help'base - print or set address offsetbdinfo - print Board Info structureboot - boot default, i.e., run 'bootcmd'bootd - boot default, i.e., run 'bootcmd'...usb - USB sub-systemusbboot - boot from USB deviceversion - print monitor, compiler and linker version
As you can see, the list is quite long; however, due to spacing reasons, we cannot report or explain all commands, so we'll take a look at the most important ones.
The help command can also be used to get more information about a command:
=> help helphelp - print command description/usageUsage:help - print brief description of all commandshelp command ...- print detailed usage of 'command'
Tip
Note that most of the commands will display their helping message when executed without any arguments. Of course, this behavior is not respected by those commands that execute with no arguments.
The environment
Before starting to take a look at the commands, we should first see one of the most important features of U-Boot: the environment. We can store whatever we need to accomplish a safe system boot in the environment. We can store variables, commands, and even complete scripts in it!
To check the environment content, we can use the print command:
The bootcmd command is the default boot command that is executed each time the system starts.
The command output is quite cryptic due the fact that the newline (\n) characters are missing (although U-Boot doesn't need them to correctly interpret a script); however, to make the output more readable, the preceding output has been rewritten here with the necessary newline characters:
if test ! -n ${dtb_name}; then
setenv dtb_name at91-${board_name}.dtb;
fi;
fatload mmc 0:1 0x21000000 /dtbs/${dtb_name};
fatload mmc 0:1 0x22000000 zImage;
bootz 0x22000000 - 0x21000000
In this case, we cannot properly talk about man pages, but if we use the help command, we can take a kind of them.
Tip
Note that the print command is just a short form of the real command printenv.
To write/modify an environment variable, we can use the setenv command:
=> setenv myvar 12345=> print myvarmyvar=12345
We can read the variable content by prefixing its name with the $ character:
=> echo "myvar is set to: $myvar"myvar is set to: 12345
In a similar manner, to write a script, we can use this:
=> setenv myscript 'while sleep 1 ; do echo "1 second is passed away.. ." ; done'
Tip
Note that we used the two ' characters to delimitate the script commands! This is to prevent U-Boot from doing some variable replacement before storing the script (it's something similar to what we do when we use the Bash shell's variable substitution).
Again, over here, we did not add the newlines; however, this time, the script is quite simple and readable. In fact, with the newline characters, the output should appear as follows:
while sleep 1 ; do
echo "1 second is passed away..." ;
done
In the end, we can run a by using the run command, as follows:
=> run myscript 1 second is passed away...1 second is passed away...1 second is passed away......
Note
We can stop the script by hitting Ctr l + C . The environment is reset each time the system starts, but it can be altered by modifying the environment file in the microSD (refer to the next section).
In case we made some errors, don't panic! We can edit the variable with this command:
=> env edit myscriptedit: while sleep 1 ; do echo "1 second is passed away..." ; done
Now we can do all the required modifications to the script in an easy manner.
Managing the storage devices
The main goal of a bootloader is to load the kernel into the memory and then execute it; to do that, we must be able to get access to all the storage devices of the board where the kernel can be located. Our boards have several storage devices; however, in this book, we'll see only two of them: MMC (or eMMC) and NAND flash.
MMC
To show MMC (Multi Media Card) management in U-Boot, we are going to use the BeagleBone Black since it has both an eMMC and a microSD port on board. As already stated in the first chapter, we're able to choose our booting device simply using the user button, so it's very important to discover how we can manage these devices, so let's power on our BeagleBone Black and then stop the bootloader by pressing the spacebar within two seconds, as shown here:
U-Boot SPL 2016.03-dirty (Apr 15 2016 - 13:44:25)Trying to boot from MMCbad magicU-Boot 2016.03-dirty (Apr 15 2016 - 13:44:25 +0200) Watchdog enabledI2C: readyDRAM: 512 MiBReset Source: Global warm SW reset has occurred.Reset Source: Power-on reset has occurred.MMC: OMAP SD/MMC: 0, OMAP SD/MMC: 1Using default environmentNet: <ethaddr> not set. Validating first E-fuse MACcpsw, usb_etherPress SPACE to abort autoboot in 2 seconds=>
Now, as already done earlier with SAMA5D3 Xplained, we can use the help command to see which are the MMC-related commands. We discover that the MMC support is implemented with the mmcinfo and mmc commands. The former can be used to get some useful information about the microSD/MMC present on the selected MMC slot, while the latter is used to effectively manage the microSD.
Let's look at some examples.
We know that our BeagleBone Black has an onboard eMMC on MMC slot 1, so to get some information about that device, we should first select the MMC slot to be examined using the following command:
=> mmc dev 1switch to partitions #0, OKmmc1(part 0) is current device
Then, we can ask for the MMC device information using the mmcinfo command:
In the same manner, we can examine the alternate booting microSD we built in Chapter 1, Installing the Developing System, and that we used to boot the system. Here is the output that appears on my system:
=> mmc dev 0switch to partitions #0, OKmmc0 is current device=> mmcinfo Device: OMAP SD/MMCManufacturer ID: 41OEM: 3432Name: SD4GBTran Speed: 50000000Rd Block Len: 512SD version 3.0High Capacity: YesCapacity: 3.7 GiBBus Width: 4-bitErase Group Size: 512 Bytes
Now we can examine the microSD partition table by using the following command:
We get only one partition where our Debian filesystem is located.
Let's examine the / (root) directory using another command that's useful in listing the content of a EXT4 filesystem, which is the command etx4ls, as shown here:
Now, as an example, in order to import the uEnv.txt file content, we can use the load command:
=> load mmc 0:1 $loadaddr /boot/uEnv.txt726 bytes read in 28 ms (24.4 KiB/s)
Tip
Note that the value for the loadaddr variable is usually defined in the default environment (that is, the default built in the U-Boot image at the compilation stage) or using the uEnv.txt configuration file.
The command loads a file from the microSD into the RAM, and then we can parse it and store the data into the environment using the env command:
=> env import -t $loadaddr $filesize
Tip
In contrast with the earlier loadaddr variable, the filesize variable is dynamically set after each file manipulation command's execution, for instance, the just used load command.
To save a variable/command in the environment (in a way that the new value is reloaded at the next boot), we can use U-Boot itself, but the procedure is quite complex and, in my humble opinion, the quickest and simplest way to do it is by just putting the microSD on a host PC and then changing the file on it!
In any case, we can take a look at the read data using the md command, as follows:
In this manner, we do a memory dump at the address specified by the loadaddr variable, where we just loaded the /boot/uEnv.txt file content.
Managing the flash
The flash memories are very useful when we don't need relatively small storage devices and we want to keep the cost of a board very low.
In the past, they represented the only (and valid) solution for reliable mass memory devices for embedded systems due to the fact that they can work in very hostile environments (temperatures under 0° C or above 80° C and dusty air) and they have no moving parts that dramatically increase the life cycle of the system.
Nowadays, they are almost replaced by eMMC memories, but they are still present on really compact and low-power systems. In this book, the only boards where we can find them are SAMA5D3 Xplained, so let's switch to this board again.
Tip
In reality, eMMC or MMC (which is not a soldered version of an eMMC) is flash memory, but it takes a specific name because there is a flash controller inside the chip that actually manages the internal flash memory. So, using these devices, we can unload our embedded kits' CPUs of the duty to manage the flash.
The flash memory in our board is a NAND flash, which is a technology that uses floating gate transistors (just like the NOR flash) but is connected in a way that resembles a NAND gate.
In this case, we have just one NAND device, so we don't need to use the nand device command to select one, as we did earlier regarding the MMC devices on our BeagleBone Black.
The check for the bad blocks (that is, those parts of the chip that are broken), we can use the following command:
=> nand badDevice 0 bad blocks:00c8000000ca0000
These blocks will never be used to store our data.
Now the last three useful commands to manage the flash content are nand erase, nand read, and nand write. The first command is split into two subcommands: nand erase.part, which erases an entire MTD partition, and nand erase.chip, which erases the entire chip.
Then, the nand.read and nand.write commands are used as they are expected, that is, to read or write a flash block. At the moment, we are not going to add examples for these actions since we still have no valid data to store in the flash; however, we're going to show how these commands can be used in the Managing a MTD device section in Chapter 5, Setting up an Embedded OS .
Also note that in the NAND flash, we can store the current environment in a similar manner as earlier with the MMC/eMMC using the saveenv command; however, in order to work, this command must be correctly configured inside the U-Boot code by the developer.
Tip
These topics are not covered in this book due to space issues and because they are almost accomplished by the board manufacturer, so we can consider them already-fixed-up. However, you can take a look at the configuration that defines CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE and friends in U-Boot's repository for further information.
GPIO management
The General Purpose Input Output (GPIO) signals are input output pins with no special purpose defined; when a developer needs one of them working as an input pin or as an output pin (or another function), they can easily reconfigure the CPU in order to accommodate their needs (GPIOs will be widely presented in Chapter 6, General Purposes Input Output signals - GPIO ).
Managing GPIO from early booting stages can be useful in selecting a specific mode of functioning: for instance, in a system that normally boots from the NAND flash, we can decide to completely erase it rewrite its contents from a file read by the MMC if a GPIO is set; otherwise, we do a normal boot.
The command to manage GPIO is gpio, and to try usage of this command, we can use the BeagleBone Black board where we can use this command to control the user LEDs. As for the other GPIO lines of the BeagleBone Black, they are mapped as follows:
So we can easily deduce that in order to toggle LED USR0, we can use the following commands:
=> gpio toggle 53gpio: pin 53 (gpio 53) value is 1=> gpio toggle 53gpio: pin 53 (gpio 53) value is 0
Of course, we can turn the LED on and off simply using the set and clear options, respectively, while the input option can be used to read the input status of the related GPIO line.
Another useful device class to get access to in early booting stages is I2C devices; in fact, these devices are commonly used to expand the CPU's peripheral set and they can be used for a large variety of purposes that, under some circumstances, must be read or set up during the boot (GPIO will be widely presented in Chapter 9, Inter-Integrated Circuit - I2C ).
As for GPIO, I2C devices are completely managed by the i2c command, and to test this command, we have to continue using the BeagleBone Black since it is the only one that has some onboard I2C devices. The list of this devices can be obtained by some simple steps; first of all, let's display a list of all available I2C buses:
By taking a look at the board's schematics, we can discover that the bus where these devices are connected to is omap24_0, so let's set it as the current bus using the following command:
=> i2c dev 0Setting bus to 0
Now we can ask the system to probe all connected devices for us with the following command:
=> i2c probeValid chip addresses: 24 34 50
Great! We found three devices; now we can try to read some data from them; in particular, we can try to read the onboard EEPROM content using the i2c md command at address 50 (which is the hexadecimal EEPROM's address):
With the preceding command, we asked to dump data from the device at address 0x50 starting from address 0x0 (expressed as a word thanks to the .2 specifier) displaying 0x20 bytes as the output.
In this output, we can recognize the header (bytes 0xaa 0x55 0x33 0xee) and then the board version.
Tip
More information on the BeagleBone Black's EEPROM contents can be taken from the BeagleBone Black's user manual.
Loading files from the network
Another useful U-Boot feature is the ability to load a file from a network connection. This feature used during the developing stages helps the developer avoid having to continuously plug and unplug the microSD card from the system; in fact, as we saw in the first chapter, the system needs the bootloaders and kernel images to start up and, in case of errors during the development of these components, we will need to replace them frequently till they are OK. Well, supposing that at least the networking function works in our U-Boot release, we can use it to load a new image in the system memory.
Let's suppose that our kernel image is not functioning as well as we need it to be; we can set up U-Boot in order to load the kernel image from the network and then boot it to do all the required tests.
The command used to do this action is tftp. This command uses the Trivial File Transfer Protocol (TFTP) protocol to download a file from a remote server.
The remote server, of course, is our host PC where we have to install a proper package with the following command:
$ sudo aptitude install tftpd
Tip
It may happen that during the installation, we get the following message:
Note: xinetd currently is not fully supported by update-inetd. Please consult /usr/share/doc/xinetd/README.De bian and itox(8).
In this case, we have to add a file named tftp into the /etc/xinetd.d directory by hand. The file should hold the following code:
# default: yes # description: The tftp server serves files using # the Trivial File Transfer # Protocol. The tftp protocol is often used to # boot diskless workstations, download # configuration files to network-aware # printers, and to start the installation # process for some operating systems. service tftp { disable = no socket_type = dgram protocol = udp wait = yes user = root server = /usr/sbin/in.tftpd server_args = -s /srv/tftpboot }
Then, we have to create the /srv/tftpboot directory, as follows:
$ sudo mkdir /srv/tftpboot
Then, restart the xinetd daemon with the usual command, as follows:
When the installation is finished, we should have a new process listening on UDP port 69:
$ netstat -lnp | grep ':\<69\>'(Not all processes could be identified, non-owned process infowill not be shown, you would have to be root to see it all.)udp 0 0 0.0.0.0:69 0.0.0.0:*
Also, the default tftpd root directory should be /srv/tftpboot, which is obviously empty:
$ ls -l /srv/tftpboot/total 0
OK; let's copy our kernel image as we did in Debian 8 (jessie) for Wandboard section in Chapter 1 , Installing the Developing System :
Note that the sudo usage in the preceding command may not be needed in every system. I used it just because my host PC didn't properly install the daemon, as reported in earlier tip section.
Now in our Wandboard, we have to set up the ipaddr environment variable to be able to ping our host PC first. As an example, my host PC has the following network configuration:
Note that on your system, both the Ethernet card name and IP address settings may vary, so you have to change the settings in order to fit your LAN configuration.
Also, my DHCP server leaves the IP addresses from 192.168.32.10 to 192.168.32.40 available for my embedded boards, so we can do the following setting in Wandboard:
=> setenv ipaddr 192.168.32.25
Now, if everything works well, we should be able to ping my host PC:
=> ping 192.168.32.43Using FEC devicehost 192.168.32.43 is alive
Great! At this point, we can try to load a file from the host PC, so we have to assign the TFTP server's IP address to the serverip variable, as follows:
=> setenv serverip 192.168.32.43
Then, we can load our kernel image using the following command:
=> tftpboot ${loadaddr} vmlinuz-4.4.7-armv7-x6 Using FEC deviceTFTP from server 192.168.32.43; our IP address is 192.168.32.25Filename 'vmlinuz-4.4.7-armv7-x6'.Load address: 0x12000000Loading: ############################################################# ############################################################# ... ############################# 688.5 KiB/sdoneBytes transferred = 5802912 (588ba0 hex)
Perfect, we did it! Now you can use the just download kernel image to continue your developing.
Tip
At the moment, this mode of operation is not explained here, but it will be explained in detail in the next chapter.
Before ending this chapter, let we address the fact that the Wandboard used only one Ethernet port, so it's quite obvious that whatever settings we do in U-Boot are referred to that device, but what happens if we have more that one device? For example, our SAMA5D3 Xplained has two Ethernet ports; how can we manage this setup if we wish to use the tftp command described earlier?
To answer this question, we have to switch the developer kit and power up SAMA5D3 Xplained. After the boot, we have to stop U-Boot and then we have to display the environment:
Looking at the defined variables, we notice that one is named ethact; this is the variable that specifies the currently used Ethernet port. In the preceding output, we read that the system is set to gmac0, which is the gigabit Ethernet port (the port is labeled ETH0/GETH on the board).
So, if we repeat the preceding setup, we should be able to ping our host PC, as did earlier:
=> setenv ipaddr 192.168.32.25=> ping 192.168.32.43gmac0: PHY present at 7gmac0: Starting autonegotiation...gmac0: Autonegotiation completegmac0: link up, 100Mbps full-duplex (lpa: 0x45e1)host 192.168.32.43 is alive
Tip
If we get the following error, it's because the Ethernet port has no default MAC address:
*** ERROR: `ethaddr' not set ping failed; host 192.168.32.43 is not alive
In this case we, have to set a random one ourselves with the following command:
=> setenv ethaddr 3e:36:65:ba:6f:be
Then, we can repeat the ping command.
Now we can repeat the ping command through the other Ethernet port (the one labeled ETH1 on the board) by changing the ethact variable, as follows:
=> setenv ethact macb0
Tip
The name we have to use is usually specified in the developer kit's documentation.
Now we can repeat the ping command:
=> ping 192.168.32.43macb0: PHY present at 0macb0: Starting autonegotiation...macb0: Autonegotiation completemacb0: link up, 100Mbps full-duplex (lpa: 0x45e1)host 192.168.32.43 is alive
Tip
If we got an error regarding the missing definition of the ethaddr variable, we should get the following one, which is related to the eth1addr variable: *** ERROR: `eth1addr' not set ping failed; host 192.168.32.43 is not alive This is in case we have to act in the same manner as earlier.
The last note is about the fact that usually, U-Boot will automatically switch the active port in case the autonegotiation fails. If our system has ethact set as gmac0 but the cable is plugged into the ETH1 port, we get the following output:
=> ping 192.168.32.25gmac0: PHY present at 7gmac0: Starting autonegotiation...gmac0: Autonegotiation timed out (status=0x7949)gmac0: link down (status: 0x7949)macb0: PHY present at 0macb0: Starting autonegotiation...macb0: Autonegotiation completemacb0: link up, 100Mbps full-duplex (lpa: 0x45e1)host 192.168.32.43 is alive
Note
We may experience some troubles regarding the networking support with the U-Boot version we're using in this book with SAMA5D3 Xplained. This is because this U-Boot release is not well aligned with the official one from Atmel. If so, don't worry, we can still use the official U-Boot release at: http://www.at91.com/linux4sam/bin/view/Linux4SAM/Sama5d3XplainedMainPage#Build_U_Boot_from_sources . That will work for sure!
The kernel command line
Before closing our tour of the bootloader, we should take a look at the way U-Boot uses to pass a command line to the kernel. This data is very important because it can be used to configure the kernel and pass some instruction to the user's programs on the root filesystem.
These arguments are stored in the bootargs variable and its setting depends on the board we are using. For example, if power on our Wandboard and, as done earlier, we stop its bootloader, as shown earlier, wecan see thatthis variable is not set at all:
=> print bootargs## Error: "bootargs" not defined
This is because its content is set up by the booting scripts that are not executed if we stop the boot. On our system, by carefully reading the U-Boot environment, we can discover that sooner or later, the run mmcargs command is called.
Note
This is because U-Boot automatically executes the script held in the bootcmd variable.
This is where the kernel command line is built. As a useful exercise, you can now try to understand which are the values used for all the earlier variables; the only thing we wish to add is that we can add our custom settings using the optargs variable.
For instance, if we wish to set the loglevel kernel (that is the lower kernel message priority showed on the serial console, as shown in the Managing the kernel messages section), we can set optargs to this:
=> setenv optargs 'loglevel=8'
And then we ask to continue the boot:
=> boot
Once the system has been restarted, we can verify the new setting by looking into the booting messages of the kernel; we should see a line as shown here:
This can be checked by looking at the procfs file, which holds a copy of the kernel command line, that is, the /proc/cmdline file using the following command: