U-Boot porting
About
This article will guide you on porting u-boot on your phone. It's possible, even if there is no complete SOC support in uboot tree (only uart driver needed), because stock bootloader initialized hardware for us. First thing to focus on should be getting u-boot shell prompt.
Why bother?
Porting u-boot to your phone gives you more control, when booting mainline kernels, namely:
- Adjusting boot command line
- Dual-boot without hacks
- Prevent stock bootloader from kernel / device tree modification
Preparation
- Find uboot code
- Before starting uboot porting, you should get access to the stock bootloader uart port. Make sure you can see stock bootloader logs.
- Search uboot code for a uart driver, compatible with your SOC. It is good, if it support debug(i.e. include debug_uart.h)
- Get familiar with you phone RAM map
- Some useful links:
- bring up process
- configuration process
Create basic configuration, building and running
Add board files
Use guide from Free Electrons pdf or video version
Examples
Configure
U-boot configuration system comes from linux kernel. Put config options not supposed to be changed by user in /include/configs/.*.h
files, in *_defconfig
otherwise
Options, you DO NOT need
- All SPL related options. SPL(Secondary Program Loader) splits u-boot in two parts. You don't need this, since stock bootloader will load whole u-boot image into RAM.
- CONFIG_BOARD_EARLY_INIT_F
Options, you need
- CONFIG_SYS_MALLOC_LEN - size of early C runtime environment heap
- CONFIG_SYS_INIT_SP_ADDR - address of early C runtime environment stack (should be in RAM address space, but NOT at the bottom, because stack is growing DOWN. see u-boot readme memory management section). This option valid, when
CONFIG_POSITION_INDEPENDENT=n
. - CONFIG_SYS_INIT_SP_BSS_OFFSET - when
CONFIG_POSITION_INDEPENDENT=y
, defines offset to early c runtime stack from bss section from bss section. Default is 512K. - CONFIG_SYS_TEXT_BASE - u-boot base address. It's the address in RAM, where u-boot is loaded by stock bootloader. Not obligatory, if you use position independent build with
CONFIG_POSITION_INDEPENDENT=y
. See instructions below, how to find it. - CONFIG_SYS_SDRAM_BASE - start address of the dynamic RAM. Can be found in memory node in linux device tree.
- CONFIG_DEBUG_UART=y
- CONFIG_DEBUG_UART_SKIP_INIT=y - make sure to comment
_debug_uart_init
function code in you serial driver with that option - CONFIG_DEBUG_UART_BASE=0x13820000 // your uart address from downstream device tree
- CONFIG_LOGLEVEL=8 // log all
- CONFIG_POSITION_INDEPENDENT - Since Galaxy S8, Samsung randomizes kernel load address. You have to enable this option, if
Kernel code
start address is random, i.e. changes each boot. FindKernel code
address withcat /proc/iomem
Building
Make u-boot
Install arm cross compiler Build:
export CROSS_COMPILE=aarch64-linux-gnu- make <$board_name>_defconfig make
Create android boot image
mkbootimg --base 0x40000000 --kernel_offset 0x00008000 --ramdisk_offset 0x01000000 --tags_offset 0x00000100 --pagesize 2048 --second_offset 0x00f00000 --kernel {path to u-boot.bin} -o {output file}
Replace offsets to your offsets, found from downstream boot.img.
Running
Flash android image, or follow Bootloaders_porting_using_linux guide
Getting uart debug output
Raw
This may be useful, when no C runtime environment set up yet. You should find uart address to write
C
*(volatile unsigned char *)(0x13820020/*uart register to write characters*/) = 0x48; // print H letter
Assembly
mov x26, #0x20 // move 0x13820020 low 16 bit to x26 register movk x26, #0x1382, lsl #16 // move 0x13820020 high 16 bit to x26 register mov x27, #0x48 // H letter ascii code str x27, [x26] // move H to uart register
Using debug() function
You can get debug output from particular *.c file by adding #define DEBUG
at the top of file, and calling debug("debug output");
where you need.
Finding base address
Unless you build u-boot with CONFIG_POSITION_INDEPENDENT=y
, you should tell u-boot base address, i.e. address in RAM, where stock bootloader load u-boot. This section describes, how to find it.
Without the knowledge of base address u-boot will fail on /common/board_f.c
file when calling functions from init_sequence_f
. This is a list of initcall addresses, and if CONFIG_SYS_TEXT_BASE does not match with address u-boot resides, you CPU will jump in wrong address and execute irrelevant code, leaving you with unpredictable uart output and results.
cat /proc/iomem | grep "Kernel code"
will output something like: 2c0400000-2c0e031d0 : Kernel code
address range start will be your CONFIG_SYS_TEXT_BASE
address.
Creating basic device tree
Will contain node for uart driver, fixed clock and chosen.
a5y17lte device tree example
exynos7880.dtsi:
// SPDX-License-Identifier: GPL-2.0+ /dts-v1/; #include "skeleton.dtsi" / { compatible = "samsung,exynos7880"; fin_pll: xxti { compatible = "fixed-clock"; clock-output-names = "fin_pll"; u-boot,dm-pre-reloc; #clock-cells = <0>; }; uart2: serial@13820000 { compatible = "samsung,exynos4210-uart"; reg = <0x13820000 0x100>; u-boot,dm-pre-reloc; }; };
a5y17lte.dts:
// SPDX-License-Identifier: GPL-2.0+ /dts-v1/; #include "exynos7880.dtsi" / { compatible = "samsung,exynos7880"; aliases { console = &uart2; }; chosen { stdout-path = &uart2; }; }; &fin_pll { clock-frequency = <26000000>; };
Final changes
You may need to comment some serial driver initialization code, as it may not work properly without clock and pinctrl drivers.
Congrats! You should now get a u-boot console prompt!
Testing future u-boot builds
Once you entered in u-boot console, you may upload new u-boot build in RAM via uart and run it
Upload a file into RAM
- on u-boot console:
load{b|x|y|s}
<address>. If no address specified, file will be loaded at CONFIG_SYS_LOAD_ADDR - exit u-boot console to release tty device
With ckermit
- install ckermit (build it yourself with `-DSCO_OSR504` flag, for 115200+ baudrates)
- create a script to send file:
#!/bin/ckermit + set port /dev/ttyUSB0 set speed 921600 set carrier-watch off set flow-control none set prefixing all send \%1 exit
====
TODO: Figure out how to transfer files from picocom with ymodem or xmodem or gkermit |
====
Run uboot test version
On u-boot console: go <address of loaded file>
Hush shell
There is scripting possibility in u-boot with hush shell.
- Build u-boot with CONFIG_HUSH_PARSER.
- Write simple
uboot_script.sh
file like (Note, that empty line in you file will repeat previous command!)
for i in a b c; do echo $i done
- Make uboot image
mkimage -T script -C none -n 'Demo Script File' -d uboot_script.sh uboot_script.img
- Upload file to RAM as described above
- Run
iminfo <address of loaded script>
- check and print script infosource <address of loaded script>
- run script
Booting linux with u-boot
Usual booting procedure involves loading u-boot's payload from storage to ram, and invoking bootm
command. If you don't have storage driver in u-boot for your SoC, you may pack u-boot's payload along with u-boot in one file. That file may be used as stock bootloader's payload. In such case, you should pay attention at CONFIG_SYS_INIT_SP_BSS_OFFSET
and CONFIG_SYS_INIT_SP_ADDR
options to avoid u-boot early c runtime screw up u-boot's payload. Default values for mentioned options is 512K. Since u-boot's size is usually less than 512K, putting u-boot's payload with 1M offset should be safe, i.e:
cp u-boot.bin stock_payload.bin
dd if=u-boot_payload.bin of=stock_payload.bin seek=1 bs=$((0x100000))
Troubleshooting
Failed to reserve memory...
You may need to increase LMB_MAX_REGIONS config to be higher, than reserved memory regions count in your linux dts. This is because u-boot tries to reserved all reserved memory regions, and it should have regions in lmb library available.