Bootloader

Kurz gesagt, ein Bootloader ist die erste Software die geladen und ausgeführt wird wenn ein Computer/Embedded System startet. Er ist dafür zuständig den Betriebssystem Kernel in den Arbeitsspeicher zu laden und auszuführen sowie einfache Hardwarekomponenten zu initialisieren die in so einem frühen Stadium benötigt werden.

Bootsequenz des Raspberry Pi

Die Bootsequenz des RPi unterteilt sich in mehere Punkte, wobei das Laden des Bootloaders und des Kernel nur einen Teil einer strikten Kette von einzelnen Schritten darstellt.

  1. Der Stage 1 Bootloader wird ausgeführt, er wird aus einem On-Chip ROM geladen. Zu seiner Aufgabe gehört es den Stage 2 Bootloader zu laden.

  2. Der Stage 2 Bootloader (bootcode.bin) wird von der SD-Karte geladen in den L2 Cache geladen. Seine Aufgabe ist es den SDRAM zu initialisieren und den Stage 3 Bootloader in den RAM zu laden.

  3. Im Stage 3 Loader (loader.bin) wird die GPU-Firmware im elf-Format gelesen (start.elf) und anschließend ausgeführt.

  4. Hier würde nun nach dem Lesen der System Konfigurationsparameter und der Kernel Parameter versucht ein Kernel Image zu laden. Allerdings haben wir hier eine zusätzliche Schicht eingezogen, die anstatt dem Kernel Image ein U-Boot-Image lädt.

  5. Der U-Boot-Bootloader kann im letzten Schritt verwendet werden um den Kernel zu starten.

Folgendes Bild veranschaulicht den Bootvorgang nochmal:

_images/pi-boot.png

Vorteile eines konfigurierbaren Bootloaders

  • Flexibilität

  • Bootargumente

  • Steuerbare Bootvorgänge

  • Booten von unterschiedlichen Medien. SD-Karte, USB-Stick, Netzwerk, etc.

U-Boot - Universal Boot Loader

Der umfangreichste und flexibelste Bootloader im Embedded Bereich ist unbestritten U-Boot. Die quelloffene Software steht unter der GNU GPL und wird aktiv weiterentwickelt. Gepflegt wird das Projekt von der DENX Software Engineering GmbH und einer stetig wachsenden Community die sich um das Projekt bebildet hat. Es werden unterschiedliche Prozessoren und Architekturfamilien z. B. PowerPC, ARM, AVR32 und MIPS unterstützt. Der Bootloader kann schon vor dem Kompilieren durch eine breite Palette von Scripten flexibel konfiguriert werden, d. h. geht es leicht von der Hand spezielle Varianten für unterschiedliche Anwendungsfälle zu generieren. Auch zur Laufzeit lässt sich das Verhalten durch umfangreiche Kommandozeilenbefehle bzw. eine Shell (Hush vom BusyBox-Projekt) sowie persistent speicherbare Umgebungsvariablen beeinflussen.

Die offizielle Dokumentation des U-Boot-Bootloaders findet sich unter: http://www.denx.de/wiki/U-Boot/Documentation

U-Boot besorgen

Der Master-Branch des U-Boot Repositories findet sich hier.

Um eine lokale Kopie dieses Repositories zu erstellen, wird git benötigt.

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

Konfiguration

Die Konfiguration von U-Boot gestaltet sich relativ einfach, da sowohl für den RPi1 als auch für den RPi2 bereits vorgefertigte Konfigurationen enthalten sind. Die Konfigurationsscripte befinden sich im Verzeichnis configs/, will man eine bestimmte Config finden verwendet man grep:

cd configs
ls | grep rpi

# Ausgabe
rpi_2_defconfig
rpi_defconfig

Beispielhaft der Inhalt der Datei rpi_2_defconfig:

CONFIG_ARM=y
CONFIG_ARCH_BCM283X=y
CONFIG_TARGET_RPI_2=y
# CONFIG_CMD_IMLS is not set
# CONFIG_CMD_FLASH is not set
# CONFIG_CMD_FPGA is not set
# CONFIG_CMD_SETEXPR is not set
CONFIG_PHYS_TO_BUS=y

Bevor man eine neue Konfiguration erstellt, sollte man den Projektordner von alten Konfigurationen befreien.

make mrproper

Im nächsten Schritt konfigurieren wir U-Boot für den Kompiliervorgang:

export CROSS_COMPILE=arm-linux-gnueabi-
make rpi_2_defconfig

# Ausgabe
HOSTCC  scripts/basic/fixdep
HOSTCC  scripts/kconfig/conf.o
SHIPPED scripts/kconfig/zconf.tab.c
SHIPPED scripts/kconfig/zconf.lex.c
SHIPPED scripts/kconfig/zconf.hash.c
HOSTCC  scripts/kconfig/zconf.tab.o
HOSTLD  scripts/kconfig/conf
#
# configuration written to .config
#

Wie beim Linux Kernel ist es nun möglich mit menuconfig, xconfig und anderen grafischen Tools die Konfiguration zu verändern. Änderungen direkt in der .config sind zwar möglich aber nicht empfohlen, weil dadurch unerfüllte Abhängigkeiten oder Widersprüche entstehen können.

Möchte man verschiede Konfigurationen erstellen und auch behalten, macht es Sinn, bei der Konfiguration einen Output Parameter mitzugeben.

Abschließend kann U-Boot kompiliert werden:

make

Ohne vorherigen export der Variablen und mit Angabe eines Output Pfades sieht das dann folgendermaßen aus.

cd /path/to/u-boot

ARCH=arm
CCPREFIX=path/to/toolchain/arm-linux-gnueabihf

make ARCH=${ARCH} CROSS_COMPILE=${CCPREFIX} V=1 O=/home/user/build/150610_rpi2_u-boot rpi_2_defconfig
make ARCH=${ARCH} CROSS_COMPILE=${CCPREFIX} V=1 O=/home/user/build/150610_rpi2_u-boot all

Die erzeugte u-boot.bin wird anstelle von kernel.img auf der SD-Karte des RaspberryPi plaziert.

Default

Die Stanndardkonfiguration läuft skriptbasiert ab, wobei typische Fälle durchprobiert werden. Diese Konfiguration sucht automatisch auch nach einem boot.scr.uimg.

Anpassungen im Betrieb

Wird die Plattform mit U-Boot gestartet, läuft zu Beginn ein Timer, der unterbrochen werden kann. Man landet dann auf einem Prompt der einige UNIX-artige Kommandos kennt.

Anpassungen im laufenden Bootloader können zwar gespeichert werden, sind dann aber in einem unbekannten Datenformat unter uboot.env hinterlegt. Man kann die Datei zwar auslesen, Änderungen können aber zu Problemen führen.

Beispielkonfiguration:

# uboot.env
arch=arm
baudrate=115200
board=rpi
board_name=rpi
boot_a_script=load ${devtype} ${devnum}:${bootpart} ${scriptaddr} ${prefix}${script};
source ${scriptaddr}boot_extlinux=sysboot ${devtype} ${devnum}:${bootpart} any ${scriptaddr} ${prefix}extlinux/extlinux.confboot_prefixes=/ /boot/boot_script_dhcp=boot.scr.uimgboot_scripts=boot.scr.uimg boot.scrboot_targets=mmc0 usb0 pxe dhcp bootcmd=run distro_bootcmdbootcmd_dhcp=usb start;
if dhcp ${scriptaddr} ${boot_script_dhcp};
then source ${scriptaddr};
fibootcmd_mmc0=setenv devnum 0;
run mmc_bootbootcmd_pxe=usb start;
dhcp;
if pxe get;
then pxe boot;
fibootcmd_usb0=setenv devnum 0;
run usb_bootbootdelay=2cpu=arm1176dhcpuboot=usb start;
dhcp u-boot.uimg;
bootmdistro_bootcmd=for target in ${boot_targets};
do run bootcmd_${target};
donefdt_addr_r=0x02000000fdtfile=bcm2835-rpi-b-rev2.dtbkernel_addr_r=0x01000000loadaddr=0x00200000mmc_boot=if mmc dev ${devnum};
then setenv devtype mmc;
run scan_dev_for_boot_part;
fipxefile_addr_r=0x00100000ramdisk_addr_r=0x02100000scan_dev_for_boot=echo Scanning ${devtype} ${devnum}:${bootpart}...;
for prefix in ${boot_prefixes};
    do run scan_dev_for_extlinux;
run scan_dev_for_scripts;
donescan_dev_for_boot_part=part list ${devtype} ${devnum} -bootable devplist;
env exists devplist || setenv devplist 1;
for bootpart in ${devplist};
    do
        if fstype ${devtype} ${devnum}:${bootpart} bootfstype;
            then run scan_dev_for_boot;
        fi;

donescan_dev_for_extlinux=if test -e ${devtype} ${devnum}:${bootpart} ${prefix}extlinux/extlinux.conf;
then echo Found ${prefix}extlinux/extlinux.conf;
run boot_extlinux; echo SCRIPT FAILED: continuing...;
fi;
scan_dev_for_scripts=for script in ${boot_scripts};
do if test -e ${devtype} ${devnum}:${bootpart} ${prefix}${script};
then echo Found U-Boot script ${prefix}${script}; run boot_a_script;
echo SCRIPT FAILED: continuing...;
fi;
donescriptaddr=0x00000000soc=bcm283xstderr=serial,vgastdin=serial,usbkbdstdout=serial,vgausb_boot=usb start;
if usb dev ${devnum};
then setenv devtype usb;
run scan_dev_for_boot_part;
fiusbethaddr=b8:27:eb:20:44:c4vendor=raspberrypi%

Anpassungen als Script

Eleganter ist der Weg über ein Script, das vom Bootloader ausgeführt wird.

echo "Be sure, my script is running!"

mmc dev 0

#setenv fdtfile bcm2835-rpi-b.dtb
#fatload mmc 0:1 ${fdt_addr_r} ${fdtfile}
setenv bootargs "dwc_otg.lpm_enable=0 earlyprintk console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait"

echo "This will take about 2 minutes, take a coffee!"
fatload mmc 0:1 ${kernel_addr_r} zImage

#bootz ${kernel_addr_r} - ${fdt_addr_r}
bootz ${kernel_addr_r}

Das Skript muss mit dem U-Boot Tool mkimage mit folgenden Parametern zu einem uimg umgewandelt werden.

mkimage -A arm -O linux -T script -C none -n boot.scr -d boot.scr boot.scr.uimg

Die erzeugte *.uimg Datei und am besten auch die *.scr Datei werden dann einfach ins selbe Verzeichnis gelegt wie die U-Boot Binärdatei.