So this is the start of a series of blog posts I announced some time ago.
I will describe here what steps you have to take to create a full Linux image for the RIoTBoard.
In the end there also will be a download to an image we created with these steps.
One for flashing with MFGTool and one for directly writing onto an SDCard.
This will include the following Blog Parts:
1. The bootloader U-Boot (This Part)
4. Flashing with MFGTool in Windows
I now updated the descriptions.
But I won´t provide any download.
What you need for this series:
- a PC or Virtual Machine with internet access and with Debian 8 Jessie installed
- access to root on the console
- the RIoTBoard of course
- optional an SDCard for Part 5
It is possible to do everything on the RIoTBoard itself, but the whole process takes way too long.
So I advise to use at least a virtual machine.
Part 1: U-Boot
In this part we will create the most essential thing of getting the board running.
The bootloader U-Boot.
1. Essentials and Compiler
First we´ll setup the build environment in Debian to compile everything.
The installed toolchain is then all you need to compile u-boot and later on also the kernel.
I´m using the Emdebian toolchain to compile everything.
If you know how, you can use another toolchain as the linaro one for example.
Open the terminal and let´s start.
First install all the essential stuff:
apt-get install git build-essential fakeroot kernel-package u-boot-tools zlib1g-dev libncurses5-dev lzop curl
In Debian Jessie there are packages integrated to create the cross build toolchain.
But we won´t build them from scratch but use the binaries provided by Emdebian.
We need to add an entry to our sources.list and add the keyring to apt.
echo 'deb http://emdebian.org/tools/debian/ jessie main' >> /etc/apt/sources.list curl http://emdebian.org/tools/debian/emdebian-toolchain-archive.key | sudo apt-key add -
Now we add the architecture for armhf and update and install the needed toolchain.
sudo dpkg --add-architecture armhf sudo apt-get update sudo apt-get install crossbuild-essential-armhf
2. Download the source code
Now that we have our environment let´s get the code.
That´s fairly easy, since all you need is the mainline source code.
It has everything integrated you need. As well as a config for the RIoTBoard.
Create a folder in which you will store everything.
I´m using the folder /home/riotboard/linux.
Change that as you like.
mkdir -p /home/riotboard/linux cd /home/riotboard/linux
Now get the code for U-Boot and cd into the new folder.
git clone git://git.denx.de/u-boot.git cd u-boot
3. U-Boot customization (Obsolete!!!)
This was done in an old version of u-boot and does not apply anmyore.
So skip this and get to step 4!
(But I´ll keep this here if anybody needs it.)
Now we get to the part of how to create our U-Boot image file, that will take the most work.
At this step we will be looking into how U-Boot works itself.
This stuff here is essential. So even if it´s a lot to read here, I advise to read it thoroughly.
U-Boot is our bootloader and it´s main purpose is, well boot our system. (Who´d thought of that...)
Simply it does that by loading the kernel into ram and execute it.
But not every system has the kernel lying around in the same place.
On one it´s written directly onto the memory at a specific address.
Others have some extra memory marked as a boot drive where everything is stored. Sometimes even on NAND memory you can´t change.
And others have just a simple file lying around in the filesystem.
And this last method is the one we want.
Simply because if you have to update the kernel, you only have to replace the old file with the new one.
And because the kernel is loaded into RAM, we can do that while the system is up an running.
After a reboot we then have the new kernel.
So how do we accomplish this in U-Boot?
In U-Boot you can execute several commands.
There are commands to select devices, list partitions, load files and so on.
And all these commands are stored in the U-Boot environment variables.
If you connect over the serial port, abort the boot process and type printenv, you can see all the defined variables.
These tell U-Boot what to do when starting.
The actual configuration of U-Boot right now, is to search for the files on an extra fat partition at the start of the flash.
Since that would mean we have to do a lot more later on when flashing everything, we want to change that.
But how?
We have two options
Option number one:
We leave those variables now as they are and just change them later when we flashed everything.
Pro:
- We don´t have to do anything now
Con:
- We need an UART serial adapter to connect on the console later on
- We have to change these variables every time we flash the system
Option number two:
We change the code inside U-Boot now, so it does what we want right after we flashed it.
Pro:
- We can reuse the image as much as we want without having to change anything right after flashing
- We don´t need an UART adapter
Con:
- We have to fiddle through the code to let it do what we want
Since we want a reusable image in the end, option one isn´t really an option here.
So we definitely will go at option two.
Luckily the only file we have to look at in the code is this one:
include/configs/embestmx6boards.h
Remember what we´re doing here right now, is being done in the code I have at this moment.
In the future this can change completely, since it already has changed once.
So let´s open that file and have a look.
(I´m using nano. If you want to use another editor feel free to do so.)
nano include/configs/embestmx6boards.h
We see a lot of lines with #define. These are the default U-Boot environment variables, that will be set when we flash the image.
Right at the start we see this line for example:
#define CONFIG_MMCROOT "/dev/mmcblk1p2"
This tells us that the root partition is at "/dev/mmcblk1p2".
That´s not what we want, so we change it to this.
#define CONFIG_MMCROOT "/dev/mmcblk0p1"
Now all the booting stuff is defined at "CONFIG_BOOTCOMMAND".
So we search for that in the file.
(Little tip for nano: Press Ctrl + W and type in what you search for)
We see this:
#define CONFIG_BOOTCOMMAND \
"mmc dev ${mmcdev};" \
"if mmc rescan; then " \
"if run loadbootscript; then " \
"run bootscript; " \
"else " \
"if run loadimage; then " \
"run mmcboot; " \
"else run netboot; " \
"fi; " \
"fi; " \
"else run netboot; fi"
Looks complicated doesn´t it?
These are just several commands that are executed at start.
Let´s look at it line by line.
Right under the definition is this line:
"mmc dev ${mmcdev};" \
This is the first command and simply just selects what device we will be using to boot from.
The device is defined a number that is stored in the variable "mmcdev".
With the dollar char and the brackets ("${variablename}") we can call any defined variable. So if we have to change it, we only have to change the variable itself and not the whole code.
The riotboard has 3 possible devices.
These are independent from the dip switch settings of the board by the way.
The dip switches only tell where to look for u-boot.
So the devices always look like this.
0 = SD Card Slot
1 = Micro SD Card Slot
2 = eMMC internal flash
Searching for the variable "mmcdev" we find this.
"mmcdev=" __stringify(CONFIG_SYS_MMC_ENV_DEV) "\0" \
So it seems that mmcdev is saved over converting the variable "CONFIG_SYS_MMC_ENV_DEV" into an string.
And this variable has the value2.
If we want to boot from eMMC that´s correct.
But what if we want to boot from an SDCard or Micro SDCard?
Like this it will always try to load the kernel from the eMMC and not the SDCard.
So we need to change "mmcdev" to try if we have an Card in one of the slots.
How do we do this?
We can´t execute that in mmcdev. So we need a function that does that for us.
Let´s create one right before mmcdev.
And we call it "loadmmcdev". (Of course we check the file first if this name isn´t already used.)
Insert the following function just one line before the line "mmcdev=" ...
"loadmmcdev=" \
"if mmc dev 0; then " \
"setenv mmcdev 0; " \
"else " \
"if mmc dev 1; then " \
"setenv mmcdev 1; " \
"else " \
"if mmc dev 2; then " \
"setenv mmcdev 2;" \
"fi; " \
"fi; " \
"fi\0" \
What does this do?
It just tries to set the device to 0 (SDCard). If it worked mmcdev will be set to 0.
If not it tries the same with 1 and then also with 2 as device.
And since we only have that three this should do the trick.
Now we need to run this function somewhere.
The easiest way to do this would be right before we actually set it.
So at "#define CONFIG_BOOTCOMMAND" we insert a line right before "mmc dev ${mmcdev};".
"run loadmmcdev; " \
So this is a little trick to get U-Boot to run from one of those three devices.
But this has a little problem here.
What if we want to boot from eMMC AND have an SDCard as a bigger storage capacity?
Or if we want to boot from an Micro SDCard and also have a normal SDCard?
Well then this won´t work of course, since we try the SDCard and if it is there we try to boot from it.
And don´t even try to boot from the Micro SDCard or eMMC.
But since we want to create an image for an SDCard later on and then boot from it we leave it like this.
So if you want to boot from eMMC and then remove the line "run loadmmcdev; " \.
And you can leave the function we inserted as it is, since it won´t do any harm without being executed.
FYI:
It is true by the way that the Dip Switches define what we boot from. But this only defines where U-Boot is loaded from.
The rest where the kernel or the rootfs lies is decided by U-Boot itself.
So there also can be combinations like having U-Boot on the eMMC, the Kernel on an Micro SDCard and the rootfs on an SDCard.
A very odd combination but it can be done.
Now let´s get further on here.
Let´s look at the next line in our boot start script.
"if mmc rescan; then " \
This line just asks if the device we just selected is available. If yes we will continue. Else the last line is executed, which starts the boot from network.
So nothing to change here.
Next we have this:
"if run loadbootscript; then " \
Now we slowly get to the interesting part.
This calls a function that loads a bootscript file into RAM.
A bootscript is just a text file in which you can execute the same commands as in the environment variables.
Since we do everything we want in the variables, we don´t need a bootscript.
Sp let´s skip to the line that´s interesting for us.
"if run loadimage; then " \
Here we go.
This line will call the commands stored in the variable "loadimage" to load our kernel image into RAM.
And on success this line will be executed:
"run mmcboot; " \
This one then will execute the commands stored in mmcboot.
So let´s find the line in which loadimage is defined.
"loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}\0" \
As you can see the command fatload is executed.
Since our image will be stored on the rootfs partition and the partition is formatted in ext2, we will change it just like that.
"loadimage=ext2load mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}\0" \
(The function is called ext2load. But in newer versions of u-boot, it is also able to load a file from an ext3 partition.)
We also need to look at the variables used here.
The only variable that need´s to be changed is "image".
Searching for image we find this:
"image=zImage\0" \
Our file will be stored in folder called "boot". So we change it to this
"image=boot/zImage\0" \
That´s it for loading the kernel image.
Next look at the functions that execute the boot commands itself.
Searching for "mmcboot" we find this:
"mmcboot=echo Booting from mmc ...; " \
"run mmcargs; " \
"if test ${boot_fdt} = yes || test ${boot_fdt} = try; then " \
"if run loadfdt; then " \
"bootz ${loadaddr} - ${fdt_addr}; " \
"else " \
"if test ${boot_fdt} = try; then " \
"bootz; " \
"else " \
"echo WARN: Cannot load the DT; " \
"fi; " \
"fi; " \
"else " \
"bootz; " \
"fi;\0" \
Oh boy this looks even more complicated doesn´t it?
Well let´s skip too much talking about this and let me tell you what is important here.
The command "bootz" is the one that executes our kernel.
But for that we need the kernel and the "flat device tree".
We already have our kernel image loaded, so we now need to load the "flat device tree" file.
This is done in the variable "loadfdt" (See the fdt for flat device tree?)
So searching for that one we find this:
"loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}\0" \
Again we want to load from ext2 instead from fat.
"loadfdt=ext2load mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}\0" \
Now we only have to look at the variable "fdt_file".
Searching for it we find out it´s stored at "CONFIG_DEFAULT_FDT_FILE".
And that looks like this:
#define CONFIG_DEFAULT_FDT_FILE "imx6dl-riotboard.dtb"
Notice that there´s more than one spot where "CONFIG_DEFAULT_FDT_FILE" can be defined.
(One for the RIoTBoard and one for the MarsBoard at this time.)
Since the devicetree file will be stored in the same folder as our kernel file, we change the line that defines it for the RIoTBoard to this:
#define CONFIG_DEFAULT_FDT_FILE "boot/imx6dl-riotboard.dtb"
And that´s all we needed to change. Everything else is set correctly and we can go on.
(We could modify the loading of the bootscript file also to load from ext2 instead of fat, but that isn´t needed right now.)
We´re done changing the file. Let´s save it (Ctrl + O) and exit the editor (Ctrl + X).
In conclusion, what is it that our U-Boot now will do on start?
First it will search for a bootscript on a fat partition. (This fails of course because we won´t even have that partition.)
Then it tries to load the image from our ext2 partition. If this works then it loads the flat device tree file and starts booting.
And if even that fails, it will try to boot with netboot. (Which we hope won´t happen of course.)
4. Compiling U-Boot
Since we´re compiling on an x86 or x64 machine we need to tell "make" that we need to use our crosscompiler.
This is done by defining environment variables that "make" understands.
Example: make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
(Don´t execute that! Just an example.)
So with this we tell "make" to use the ARM architecture and our compiler is arm-linux-gnueabihf-
The "-" at the end is important, because "make" inserts everything after that itself.
Because we don´t want to type that in every line when we execute the compiler, we just define these environment variables globally.
Also we will define an output path in which everything will be copied when it´s done.
(These environment variables are volatile by the way. In the next session if you login they´re gone and you have to set them again.)
export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- export OUTPUT=/home/riotboard/linux/output
Now we create the config so the compiler knows what device this is for.
Since there is a default config for the RIoTBoard integrated all we need to do is this.
make riotboard_defconfig
Now everything is set and ready to go.
Just execute the compiler.
make -j4
If no errors occur, you now have an u-boot.imx lying in the root folder.
ls -l u-boot.imx
That command should give the following:
-rw-r--r-- 1 root root 338944 Sep 15 16:03 u-boot.imx
(Note that the size can chage due to changes in the source code.)
Copy that file somewhere you´ll find it again.
mkdir -p /home/riotboard/linux/output cp u-boot.imx $OUTPUT/u-boot.imx
5. Bootscript
Since the method in Step 3 doesn´t apply any more, we´ll do things a little different now.
Also a lot easier.
This is an example of the most simple bootscript.
setenv bootargs console=ttymxc1,115200 nosmp video=mxcfb0:dev=hdmi,1280x720M@60,bpp=32 video=mxcfb1:off fbmem=10M vmalloc=400M rootwait root=/dev/mmcblk0p1
mmc dev 2
ext4load mmc 2 10800000 /boot/zImage
ext4load mmc 2 16800000 /boot/imx6dl-riotboard.dtb
bootz 10800000 - 16800000
But we can´t just create a textfile and leave it like that.
Wen need to "compile" a working bootscript file out of this with the help of mkimage.
So let´s create a file with our bootscript and then create the "compiled" file.
cat <<EOT > bootscript setenv bootargs console=ttymxc1,115200 nosmp video=mxcfb0:dev=hdmi,1280x720M@60,bpp=32 video=mxcfb1:off fbmem=10M vmalloc=400M rootwait root=/dev/mmcblk0p1 mmc dev 2 ext4load mmc 2 10800000 /boot/zImage ext4load mmc 2 16800000 /boot/imx6dl-riotboard.dtb bootz 10800000 - 16800000 EOT
mkimage -A arm -O linux -T script -C none -a 0 -e 0 -n "boot script" -d bootscript boot.scr
We now have a file called "boot.scr", that u-boot automatically detects when the file is on the first partition of the boot device.
Let´s copy it into the output folder.
cp boot.scr $OUTPUT/boot.scr
And that´s it for this part.
I will be explaining later on how to make use of U-Boot on the RIoTBoard.
But first we need some other stuff like the kernel and the system itself.