U-Boot is een bootmanager die we met op Bash lijkende opdrachten kunnen aansturen. Het is mij ook duidelijk dat deze omgeving eigenlijk onafhankelijk van het te starten besturingssysteem moet zijn, iets waar veel fabrikanten tegen zondigen.
Henk van de Kamer
De afgelopen 23 jaar heb ik via Het Lab duidelijk gemaakt dat ik graag exact wil weten hoe iets werkt. Qua embedded systemen was dat een proces van tien jaar, zoals ik in de vorige aflevering beschreef. Ik beloofde ook uit te zullen leggen hoe we U-Boot kunnen compileren, installeren en configureren op een Banana Pi M2 Ultra en dat gaan we hier doen. Op mijn website kun je ondertussen ook de procedure vinden voor een Banana Pi R2 en de NanoPi Neo, die ik elders in dit nummer omtover tot printserver.
ARMBIAN
Het compileren van U-Boot is op zich niet moeilijk. Het probleem is dat mijn werksysteem een Intel Core i5- en de Banana Pi M2 Ultra een ARMv7 (armv7l)-processor bevat. Gecompileerde code op het eerste systeem zal op het laatste niet werken. De oplossing is cross-compileren, oftewel tegen de compiler zeggen dat niet de x86_64-, maar de armv7l-code verwacht wordt. Vroeger was dat zeer lastig om te configureren en hoewel het tegenwoordig makkelijker is, ben ik hier nog steeds geen fan van.
Persoonlijk compileer ik liever op het systeem zelf. Omdat de processor minder krachtig is, kost dat meer tijd, maar voorkom ik ook allerlei vage problemen die het cross-compileren met zich mee kan brengen. Waarmee ik nu een kip-en-ei-situatie beschrijf. Gelukkig zijn er mensen die al een werkende Linux-versie inclusief U-Boot voor de armv7l hebben gecompileerd. De download is te verkrijgen via het Armbian-project. In feite is dit grotendeels gewoon de Debian-distributie met wat eigen unieke pakketten. De Armbian-image kunnen we op een micro-sd-kaart wegschrijven, waarna we op het systeem zelf kunnen gaan compileren.
COMPILEREN
De Banana Pi M2 Ultra wordt in zowel U-Boot als de kernel ondersteund. De beschikbare configuratie snapt verder dat de eMMC, een chip met flashgeheugen die op de printplaat is gesoldeerd, drie delen bevat:
root@bpim2u:~# lsblk /dev/mmcblk2*
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
mmcblk2 179:8 0 7.3G 0 disk
mmcblk2boot0 179:16 0 4M 1 disk
mmcblk2boot1 179:24 0 4M 1 disk
De grootste (mmcblk2) kan als normale flashdrive gebruikt worden. Oftewel: indelen in partities alsof het een harddisk is. In theorie kunnen we hierop ook een besturingssysteem installeren, maar persoonlijk zie ik die liever op een echte harddisk. De andere twee worden door de BROM - Boot ROM - gebruikt en je begrijpt dat er op deze manier een back-up mogelijk is voor het geval de ander niet meer wil starten. Voor het compileren van U-Boot is een hele serie extra pakketten nodig, waarna de volgende opdrachten gegeven kunnen worden:
hvdkamer@bpim2u:~/uboot$ make Bananapi_M2_Ultra_defconfig
hvdkamer@bpim2u:~/uboot$ make
Dat kost op het systeem zelf circa negen minuten, waarna we een ruim 476 KB groot bestand hebben. Dit kunnen we vervolgens installeren:
root@bpim2u:~# echo 0 > /sys/block/ mmcblk2boot0/force_ro
root@bpim2u:~# mmc bootbus set single_hs x1 x4 / dev/mmcblk2
root@bpim2u:~# mmc bootpart enable 1 1 /dev/ mmcblk2
root@bpim2u:~# dd if=/home/hvdkamer/uboot/uboot-sunxi-with-spl.bin of=/dev/mmcblk2boot0 bs=4k
Beide bootblokken zijn standaard read-only en met de eerste opdracht maken we het geheel tijdelijk beschrijfbaar. De volgende twee opdrachten zorgen ervoor dat de BROM het geheel als bruikbaar ziet. De laatste opdracht ‘installeert’ de gecompileerde U-Boot in het eerste bootblok. We hebben gezien dat deze ten hoogste 4 MB groot is, dus we gebruiken minder dan een achtste.
WERKT HET?
De volgende stap is uiteraard controleren of het werkt. De BROM zoekt als eerste naar de micro-sd-kaart, dus die moeten we tijdelijk verwijderen. Omdat we geen normaal beeldscherm en toetsenbord kunnen gebruiken, is een seriële verbinding nodig. Daarvoor verwijs ik je naar internet. De bootlog (hetlab.tk/downloads/bootlogs/ bootlog_bpim2u_2.txt) laat zien dat er van alles gebeurt. Als U-Boot geen configuratie kan vinden, wordt een interne versie gebruikt en die probeert op meer locaties dan de BROM de Linux-kernel te vinden. Eén daarvan is de SATA-harddisk, maar deze was de dagen ervoor getest op mogelijk slechte sectoren (oftewel effectief gewist). Nadat ook booten via het netwerk niet lukt, krijgen we een U-Boot-prompt waar we om meer hulp kunnen vragen. Na het plaatsen van de micro-sd-kaart en diens U-Boot onderbreken, kunnen we de volgende commando’s geven:
=> mmc rescan
=> mmc list
mmc@1c0f000: 0 (SD)
mmc@1c10000: 2
mmc@1c11000: 1 (eMMC)
Geen idee wat nummer 2 is, maar de andere twee mogen duidelijk zijn.
FAT-PARTITIE
Uit de bootlog wordt duidelijk dat U-Boot zijn opdrachten in een bestand op een FAT-partitie wil bewaren. Dit moet tevens de eerste partitie zijn. Ik heb daarom deze als een 40 MB grote versie op het normale deel van de eMMC aangemaakt, waarna we de U-Boot-opdrachten kunnen aanpassen en bewaren:
=> env save
=> ls mmc 1:1 / 131072 uboot.env
1 file(s), 0 dir(s)
De eerste 1 in de tweede opdracht is het device-nummer dat aan eMMC is toegekend. De tweede 1 is de eerste partitie op dit device. De rest spreekt waarschijnlijk voor zich, zeker voor wie vaker de opdrachtprompt van Linux gebruikt. Door vervolgens weer Armbian vanaf de micro-sd-kaart te starten, kunnen we dit bestand uitlezen. Dit lijkt binair, maar dat komt omdat de eerste vier bytes een CRC32-checksum zijn en alle tekst regels eindigen met een nul byte. Tijdens het compileren van U-Boot wordt ook een mkenvimage-programma gemaakt dat een normaal tekstbestand converteert naar deze deels binaire variant.
U-BOOT SCRIPT
De interne versie die we hierboven hebben opgeslagen, ziet er complex uit. Maar na een paar dagen slopen, zoeken en experimenteren snap ik nu de werking. Nadat U-Boot het bestand heeft geladen, wordt als eerste de opdracht in de bootcmd-variabele uitgevoerd. De opdracht is een simpeler variant van de Bash shell die onder Linux gebruikelijk is. Mijn versie luidt:
bootcmd=echo “eMMC version”; run bootcmd_scsi
bootcmd_scsi=dev=”scsi 0:2”; rdev=/dev/sda2; run bootcmd_tmp
bootcmd_tmp=env set bootargs “root=${rdev} rootwait console=${console}”; scsi reset; load ${dev} ${kernel_addr_r} /vmlinuz; load ${dev} ${fdt_addr_r} /dtb; load ${dev} ${ramdisk_addr_r} /initrd.img; bootz ${kernel_addr_r} ${ramdisk_addr_r}:$filesize ${fdt_addr_r}
Dit ziet er nog steeds complex uit, maar is uitgekleed tot het absolute minimum! De eerste regel zorgt voor de aangegeven melding in de bootlog. Vervolgens heb ik een serie bootcmd_xxx-regels gemaakt en standaard wordt dus de genoemde uitgevoerd. Lees de scsi als sata, de reden voor de eerste is historisch. In de volgende opdracht maak ik twee tijdelijke variabelen aan die nodig zijn om de kernel, initrd en dtb te vinden. Die laatste bevat een beschrijving van de aanwezige hardware, zodat we een generieke kernel kunnen gebruiken voor verschillende apparaten met een armv7l-processor. Je ziet dat we al deze bestanden inlezen vanaf de eerste harddisk - deze tellen we opeens vanaf nul - en daarop de tweede partitie. Dit is een Debian-installatie die ik via de bootstrap op de harddisk heb geïnstalleerd. De variabelen zoals console en kernel_addr_r zijn overgenomen uit de interne versie.
TOT SLOT
Met bovenstaande configuratie op de Banana Pi M2 Ultra heb ik nu een normale Debian-installatie (alle pakketten komen daarvandaan) op de harddisk draaien. Het U-Boot-deel staat op het eMMC-geheugen en hoeft in principe niet meer aangepast te worden. We kunnen U-Boot onderbreken en handmatig bijvoorbeeld een herstelsysteem vanaf een usb-stick starten. Met U-Boot is het ook mogelijk om een GPIO-pin uit te lezen en op basis daarvan de gewenste locatie aan te geven.
Ik zou het komende jaar probleemloos alle Het Lab-afleveringen vol kunnen schrijven over U-Boot, maar dat zal ik jullie besparen. Wie er meer over wil weten en er met mijn website niet uitkomt, kan mij altijd een mail sturen...