Jump to content

U-Boot porting

From postmarketOS Wiki


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:

  1. Adjusting boot command line
  2. Dual-boot without hacks
  3. Prevent stock bootloader from kernel / device tree modification


Preparation

  1. Find uboot code
  2. Before starting uboot porting, you should get access to the stock bootloader uart port. Make sure you can see stock bootloader logs.
  3. Search uboot code for a uart driver, compatible with your SOC. It is good, if it support debug(i.e. include debug_uart.h)
  4. Get familiar with you phone RAM map
  5. 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

  1. Add exynos7420 espresso board
  2. Add exynos7420 SOC support

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. Find Kernel code address with cat /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

You should tell u-boot base address, i.e. address in RAM, where stock bootloader load u-boot. Keep in mind, that stock bootloader may ignore offsets, set in boot.img. 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.

Easy way

cat /proc/iomem | grep "Kernel code" - address range start will be your base address.

Hard way

Dump RAM, where u-boot presumably resides, and find(hex search), at what address uboot image starts.

Enable debug

- put #define DEBUG at the top of /common/board_f.c file

Narrow down RAM range u-boot resides in

- read PC(program counter) or LR(link register) at board_init_f function

uint64_t pc;
asm volatile ("ADR %0, ." : "=r"(pc) ::);
debug("pc reg at board_init_f is: %p", pc); 

Here we are reading pc reg from C

Once you have PC register value, u-boot base address 100% in range PC-uboot_size - PC+uboot_size.

How to dump ram

- put ram dump code at the beginning of board_init_f function.


Ram dump code example:

   char *val;
   val = 0x40001000; // dump start address
   debug("0x40001000 - 0x40101000 \n");
   for(int i=0 ; i < 0x100000 /* dump size*/; i++) {
           if ((i & 0xf) == 0) {// new line every 16 symbols
               debug("\n%.8x: ", val + i); // current address stamp
           }
           debug("%.2x ", *(val + i));
       }
   debug("\n");

Find uboot start address in dump

Find first few bytes of compiled u-boot image in RAM dump. Result will be u-boot 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


Source

====

TODO 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 info
    • source <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.