Having full control of the boot process is always great. Either because you want to use features the original boot loader doesn't support. Most times a better compression, device-tree support or ubifs to improve resilance against bad blocks. Having the control also improves security because you know what you compiled and with reproducible builds there is also a relation between your source code and your binary. The reasons against replacing the bootloader are usually breaking the device if something bad happens (and you don't have the tools available to get it back into a working state). Or if you want to make it easier to get back to the original state of the device with the OEM firmware. Both doesn't count if you develop a new device or product before it gets released.
For my current project I don't have JTAG working neither is the flash chip on a socket. So testing a new bootloader is hard. In case of a failure I would have to desolder the chip, flash it and solder it again. Flashing it ISP (In-System-Programming) doesn't work often with wifi router or CPEs. If I'm desoldering a flash I always try to add a socket. I can recommend the qspimux from Felix Held. It comes with sockets and a qspimux to switch between the programmer and the board.
For testing the basic functions I would like to boot the u-boot from the current u-boot. At least I can test the higher functions. Does ethernet works to load a second bootloader? Can I boot from the second u-boot via tftp my u-boot?
OpenWrt also have a history of booting u-boot from u-boot in case the old bootloader is too buggy or have other shortcomings, some platforms just add a second u-boot (e.g. kirkwood platform).
To allow us to boot u-boot from u-boot, we should know what's the job of the the first u-boot? Must the hardware in a specific state? And what would be the difference between the first and second u-boot? To get started, everything depend on the architecture (arm, mips, arm64, ..) and the specific SoC what the first u-boot really does.
I've a board with a Mediatek mt7622 SoC based on arm64 (to be precise 4x ARM Cortex A53 cores). We could take a look into the arm manuals how its booting. A summary can be found in the arm trusted firmware repository. The main question we have to find or just test, are there parts of the SoC or components which can't or must not be initialized twice? We should not re-initialize the memory controller if our second u-boot lies in memory.
The good point is the u-boot doesn't do memory initialisation and training it's done by the arm trusted firmware. Another thing we don't have to disable. Let's create a first u-boot image for us using OpenWrt. I expect you already added your board to the u-boot. Otherwise we could just use the already supported mt7622_bananapi_bpi-r64-snand.
# apt install quilt flex bison cd openwrt # I'm using OpenWrt, current master of revision c3322cf04a1b9ee826dcc56944750b6bbcb716ef # Select your board, and config. # Ensure Boot Loaders -> u-boot-mt7622_bananapi_bpi-r64-snand is enabled make menuconfig make package/boot/arm-trusted-firmware-mediatek/clean package/boot/arm-trusted-firmware-mediatek/compile make package/uboot-mediatek/clean package/uboot-mediatek/prepare QUILT=1 cd build_dir/target-aarch64_cortex-a53_musl/u-boot-mt7622_mt7622_bananapi_bpi-r64-snand/u-boot-2022.10/ quilt push -a # now lets change the u-boot config make mt7622_bananapi_bpi-r64-snand_defconfig make menuconfig # choose * "Skip the call to lowlevel_init during early boot ONLY" (CONFIG_SKIP_LOWLEVEL_INIT_ONLY) * "Boot options" -> "Boot Images" -> "Text Base" = 0x43e00000 (CONFIG_SYS_TEXT_BASE) # now go back to the openwrt top dir (where you checkout OpenWrt) cd openwrt/ make package/uboot-mediatek/compile V=s cd build_dir/target-aarch64_cortex-a53_musl/u-boot-mt7622_mt7622_bananapi_bpi-r64-snand/u-boot-2022.10/ ls u-boot* # u-boot: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped # u-boot-dtb.bin: u-boot binary with device-tree binary appeneded # u-boot-nodtb.bin: u-boot binary without device-tree binary # u-boot.bin: same as u-boot-dtb.bin # u-boot.cfg: The build config as c defines # u-boot.fip: u-boot binary packed to be loaded by the arm trusted firmware # u-boot.lds: linker script (can be ignored) # u-boot.map: linker map file (can be ignored) cp u-boot-dtb.bin /srv/tftp/
Now I tried to upload the whole binary to from the first u-boot via tftp
# u-boot shell MT7622> tftpboot 0x43e00000 u-boot-dtb.bin MT7622> go 0x43e00000 ## Starting application at 0x43e00000 ... data abort pc : [<4007ff34>] lr : [<5ef571e4>] sp : 5cf4fe28 ip : 00000030 fp : 5cf5a818 r10: 00000002 r9 : 5cf4ff40 r8 : 5efa3784 r7 : 5cf5b2d8 r6 : 4007ff28 r5 : 00000002 r4 : 5cf5b2dc r3 : 4007ff28 r2 : 5cf5b2dc r1 : 5cf5b2dc r0 : 00000001 Flags: nZCv IRQs off FIQs off Mode SVC_32 Resetting CPU ...
And now it crashs. Let's take a look back into the u-boot
cd openwrt cd build_dir/target-aarch64_cortex-a53_musl/u-boot-mt7622_mt7622_bananapi_bpi-r64-snand/u-boot-2022.10/ # first find instruction of the u-boot. It's usually the start.S. We're lazy and search for start.o. The compiled version. find -iname 'start.o' ## ./arch/arm/cpu/armv8/start.o # The start.S is in the same directory. Lets have a look into it with your favourite editor vim ./arch/arm/cpu/armv8/start.S
// [..] #include <asm/arch/boot0.h> #else b reset #endif .align 3 .globl _TEXT_BASE _TEXT_BASE: .quad CONFIG_SYS_TEXT_BASE /* * These are defined in the linker script. */ .globl _end_ofs _end_ofs: .quad _end - _start .globl _bss_start_ofs _bss_start_ofs: .quad __bss_start - _start .globl _bss_end_ofs _bss_end_ofs: .quad __bss_end - _start reset: /* Allow the board to save important registers */ b save_boot_params .globl save_boot_params_ret save_boot_params_ret: // [..]
Ok The first instruction should be a branch (or jump) to the marker reset. Let's have a short look into the u-boot-dtb.bin.
cd openwrt/ export PATH=$PATH:$(pwd)/staging_dir/toolchain-aarch64_cortex-a53_gcc-11.3.0_musl/bin cd build_dir/target-aarch64_cortex-a53_musl/u-boot-mt7622_mt7622_bananapi_bpi-r64-snand/u-boot-2022.10/ # the u-boot file is a elf binary aarch64-openwrt-linux-objdump -D ./u-boot
0000000041e00000 <__image_copy_start>: 41e00000: 1400000a b 41e00028 <reset> 41e00004: d503201f nop 0000000041e00008 <_TEXT_BASE>: 41e00008: 41e00000 .inst 0x41e00000 ; undefined 41e0000c: 00000000 udf #0 0000000041e00010 <_end_ofs>: 41e00010: 000f21f8 .inst 0x000f21f8 ; undefined 41e00014: 00000000 udf #0 0000000041e00018 <_bss_start_ofs>: 41e00018: 000f21f8 .inst 0x000f21f8 ; undefined 41e0001c: 00000000 udf #0 0000000041e00020 <_bss_end_ofs>: 41e00020: 000fe8c8 .inst 0x000fe8c8 ; undefined 41e00024: 00000000 udf #0 0000000041e00028 <reset>: 41e00028: 14000053 b 41e00174 <save_boot_params> 0000000041e0002c <save_boot_params_ret>: 41e0002c: 10fffea0 adr x0, 41e00000 <__image_copy_start> 41e00030: f2402c00 ands x0, x0, #0xfff 41e00034: 54000060 b.eq 41e00040 <pie_fixup> // b.none 41e00038: d503207f wfi 41e0003c: 17ffffff b 41e00038 <save_boot_params_ret+0xc> [..]
Ok. This looks good. Maybe the u-boot go command is missing some handover or doesn't initialize the some cpu register or other state? My first u-boot doesn't support elf binary. But it supports the uimage file format.
cd openwrt/ cd build_dir/target-aarch64_cortex-a53_musl/u-boot-mt7622_mt7622_bananapi_bpi-r64-snand/u-boot-2022.10/ mkimage -A arm -O linux -T standalone -C none -a 0x43e00000 -e 0x43e00000 -d ./u-boot-dtb.bin /srv/tftp/u-boot.image
# u-boot shell MT7622> tftpboot 0x41000000 u-boot.image MT7622> bootm 0x41000000 U-Boot 2022.10-OpenWrt-r21176-c32b761842a8 (Nov 15 2022 - 19:27:37 +0000) CPU: MediaTek MT7622 ...
Finally we managed to boot it. Now we can test the u-boot-env, booting via tftpboot and so on.
EDIT: Fixed 3x typos in the how to build receipt. The u-boot mediatek package in OpenWrt is called uboot-mediatek and not u-boot-mediatek