Giới thiệu
Việc nắm vững quy trình khởi động của U-Boot giúp hiểu rõ về các thiết bị ngoại vi được khởi tạo ở đâu, từ đó khi cần điều chỉnh các trình điều khiển thiết bị ngoại vi, chúng ta sẽ có cơ sở rõ ràng.
I. Linker Script u-boot.lds
Để phân tích quy trình khởi động của U-Boot, trước tiên cần xác định "điểm vào" (entry point) - dòng lệnh đầu tiên của chương trình. Chương trình được liên kết thông qua linker script, do đó có thể tìm thấy điểm vào thông qua linker script này. Nếu chưa biên dịch U-Boot, linker script nằm tại arch/arm/cpu/u-boot.lds. Tuy nhiên, đây không phải là linker script cuối cùng được sử dụng - nó chỉ là cơ sở để tạo ra script thực tế. Sau khi biên dịch U-Boot, file u-boot.lds sẽ được tạo ra trong thư mục gốc của U-Boot.
Mở file u-boot.lds:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(boot_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
*(.vectors)
arch/arm/cpu/armv7/start.o (.text*)
}
.dynsym _image_binary_end : { *(.dynsym) }
.dynbss : { *(.dynbss) }
.dynstr : { *(.dynstr*) }
.dynamic : { *(.dynamic*) }
.plt : { *(.plt*) }
.interp : { *(.interp*) }
.gnu.hash : { *(.gnu.hash) }
.gnu : { *(.gnu*) }
.ARM.exidx : { *(.ARM.exidx*) }
.gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}
ENTRY(boot_start) xác định điểm vào của chương trình là boot_start. Hàm boot_start được định nghĩa trong file arch/arm/lib/vectors.S:
.globl boot_start
.section ".vectors", "ax"
boot_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
ARM_VECTORS
#endif
.globl reset_handler
.globl undefined_handler
.globl swi_handler
.globl prefetch_handler
.globl abort_handler
.globl not_used_handler
.globl irq_handler
.globl fiq_handler
Phía sau boot_start là bảng vector ngắt (interrupt vector table). Chỉ thị .section dùng để chỉ định đoạn lưu trữ code; ".vectors" là tên đoạn; "ax" biểu thị đoạn có thể thực thi (executable) và ghi được (writable).
Sử dụng lệnh sau để tìm "__image_copy_start" trong U-Boot:
grep -nR "__image_copy_start"
Mở file u-boot.map để xem vị trí này. File u-boot.map là file ánh xạ của U-Boot, cho phép xem một file hoặc hàm được liên kết đến địa chỉ nào. Từ dòng 1629 có thể thấy __image_copy_start có giá trị 0XC0100000, và địa chỉ bắt đầu của .text cũng là 0XC0100000.
Từ đó có thể kết luận code trong vectors.S được lưu trong segment vectors. Địa chỉ bắt đầu của segment vectors là 0XC0100000, cho thấy toàn bộ U-Boot bắt đầu từ địa chỉ này.
II. Quy trình khởi động U-Boot
2.1 Hàm reset_handler
Từ u-boot.lds đã xác định được điểm vào là boot_start trong file arch/arm/lib/vectors.S.
.globl boot_start
.section ".vectors", "ax"
boot_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
ARM_VECTORS
#endif
.globl reset_handler
.globl undefined_handler
.globl swi_handler
.globl prefetch_handler
.globl abort_handler
.globl not_used_handler
.globl irq_handler
.globl fiq_handler
Hàm reset_handler nằm trong file arch/arm/cpu/armv7/start.S:
/*************************************************************************
*
* Startup Code (reset vector)
*
* Do important init only if we don't start from memory!
* Setup memory and board specific bits prior to relocation.
* Relocate armboot to ram. Setup stack.
*
*************************************************************************/
.globl reset_handler
.globl save_params_ret
.type save_params_ret,%function
#ifdef CONFIG_ARMV7_LPAE
.global switch_to_hypervisor_ret
#endif
reset_handler:
/* Allow the board to save important registers */
b save_boot_params
Hàm save_boot_params cũng được định nghĩa trong start.S:
/*************************************************************************
*
* void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)
* __attribute__((weak));
*
* Stack pointer is not yet initialized at this moment
* Don't save anything to stack even if compiled with -O0
*
*************************************************************************/
ENTRY(save_boot_params)
b save_params_ret
Hàm save_params_ret:
save_params_ret:
#ifdef CONFIG_ARMV7_LPAE
/*
* check for Hypervisor support
*/
mrc p15, 0, r0, c0, c1, 1
and r0, r0, #CPUID_ARM_VIRT_MASK
cmp r0, #(1 << CPUID_ARM_VIRT_SHIFT)
beq switch_to_hypervisor
switch_to_hypervisor_ret:
#endif
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
mrs r0, cpsr
and r1, r0, #0x1f
teq r1, #0x1a
bicne r0, r0, #0x1f
orrne r0, r0, #0x13
orr r0, r0, #0xc0
msr cpsr,r0
Đoạn code này thực hiện:
- Đọc giá trị thanh ghi cpsr vào r0
- Trích xuất 5 bit thấp (bit0-bit4) để xác định chế độ hoạt động của processor
- Kiểm tra xem processor có đang ở chế độ Hyp hay không
- Nếu không ở chế độ Hyp, xóa các bit mode và thiết lập chế độ SVC
- Vô hiệu hóa cả hai ngắt FIQ và IRQ bằng cách set bit I và F
| M[4:0] | Chế độ |
| 10000 | User(usr) |
| 10001 | FIQ(fiq) |
| 10010 | IRQ(irq) |
| 10011 | Supervisor(svc) |
| 10110 | Monitor(mon) |
| 10111 | Abort(abt) |
| 11010 | Hyp(hyp) |
| 11011 | Undefined(und) |
| 11111 | System(sys) |
Tiếp theo, code thực hiện thiết lập vector table:
/*
* Setup vector:
* (OMAP4 spl TEXT_BASE is not 32 byte aligned.
* Continue to use ROM code vector only in OMAP4 spl)
*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0
bic r0, #CR_V
mcr p15, 0, r0, c1, c0, 0
#ifdef CONFIG_HAS_VBAR
/* Set vector address in CP15 VBAR register */
ldr r0, =boot_start
mcr p15, 0, r0, c12, c0, 0
#endif
#endif
CR_V được định nghĩa trong arch/arm/include/asm/system.h:
#define CR_V (1 << 13)
Bit13 của thanh ghi SCTLR là bit V - điều khiển vị trí vector table:
- Khi V=0: Vector table tại 0x00000000, có thể di chuyển được
- Khi V=1: Vector table tại 0xFFFF0000, không thể di chuyển
Việc clear bit V nhằm chuẩn bị cho việc di chuyển vector table.
Tiếp tục thực thi:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
#ifdef CONFIG_CPU_V7A
bl cpu_init_cp15
#endif
#ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
bl cpu_init_crit
#endif
#endif
Hàm cpu_init_cp15 thực hiện cấu hình các thanh ghi CP15:
ENTRY(cpu_init_cp15)
/*
* Invalidate L1 I/D
*/
mov r0, #0
mcr p15, 0, r0, c8, c7, 0
mcr p15, 0, r0, c7, c5, 0
mcr p15, 0, r0, c7, c5, 6
mcr p15, 0, r0, c7, c10, 4
mcr p15, 0, r0, c7, c5, 4
...
mov pc, r5
ENDPROC(cpu_init_cp15)
Hàm cpu_init_crit cũng được định nghĩa trong start.S:
ENTRY(cpu_init_crit)
/*
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
b lowlevel_init
ENDPROC(cpu_init_crit)
Hàm cpu_init_crit chỉ gọi hàm lowlevel_init.
2.2 Hàm lowlevel_init
Hàm lowlevel_init chủ yếu khởi tạo RAM bằng cách ghi các thanh ghi điều khiển RAM.
Hàm lowlevel_init được định nghĩa trong arch/arm/cpu/armv7/lowlevel_init.S:
#include
#include
#include
.pushsection .text.s_init, "ax"
WEAK(s_init)
bx lr
ENDPROC(s_init)
.popsection
.pushsection .text.lowlevel_init, "ax"
WEAK(lowlevel_init)
/*
* Setup a temporary stack. Global data is not available yet.
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =CONFIG_SPL_STACK
#else
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
#endif
bic sp, sp, #7
#ifdef CONFIG_SPL_DM
mov r9, #0
#else
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
#endif
#endif
push {ip, lr}
bl s_init
pop {ip, pc}
ENDPROC(lowlevel_init)
.popsection
CONFIG_SYS_INIT_SP_ADDR được định nghĩa trong include/configs/stm32mp1.h:
#define CONFIG_SYS_SDRAM_BASE STM32_DDR_BASE
#define CONFIG_SYS_INIT_SP_ADDR CONFIG_SYS_TEXT_BASE
CONFIG_SYS_TEXT_BASE được định nghĩa trong file .config, do đó CONFIG_SYS_INIT_SP_ADDR có giá trị 0XC0100000. Stack pointer (sp) sẽ trỏ đến 0XC0100000.
GD_SIZE được định nghĩa:
#define GENERATED_GBL_DATA_SIZE 240
#define GENERATED_BD_INFO_SIZE 80
#define GD_SIZE 240
#define GD_BD 0
#define GD_MALLOC_BASE 164
#define GD_RELOCADDR 64
#define GD_RELOC_OFF 84
#define GD_START_ADDR_SP 80
#define GD_NEW_GD 88
Sau khi lowlevel_init hoàn thành, quay lại hàm cpu_init_crit, rồi quay về save_params_ret, tiếp theo sẽ thực thi hàm _main.
2.3 Hàm _main
Hàm _main được định nghĩa trong file arch/arm/lib/crt0.S:
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
#if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)
ldr r0, =(CONFIG_TPL_STACK)
#elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr r0, =(CONFIG_SPL_STACK)
#else
ldr r0, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
bic r0, r0, #7
mov sp, r0
bl board_init_f_alloc_reserve
mov sp, r0
mov r9, r0
bl board_init_f_init_reserve
#if defined(CONFIG_SPL_EARLY_BSS)
SPL_CLEAR_BSS
#endif
mov r0, #0
bl board_init_f
#if ! defined(CONFIG_SPL_BUILD)
/*
* Set up intermediate environment (new sp and gd) and call
* relocate_code(addr_moni). Trick here is that we'll return
* 'here' but relocated.
*/
ldr r0, [r9, #GD_START_ADDR_SP]
bic r0, r0, #7
mov sp, r0
ldr r9, [r9, #GD_NEW_GD]
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF]
add lr, lr, r0
#if defined(CONFIG_CPU_V7M)
orr lr, #1
#endif
ldr r0, [r9, #GD_RELOCADDR]
b relocate_code
here:
bl relocate_vectors
bl c_runtime_cpu_setup
#endif
#if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)
#if !defined(CONFIG_SPL_EARLY_BSS)
SPL_CLEAR_BSS
#endif
#ifdef CONFIG_SPL_BUILD
bl spl_relocate_stack_gd
cmp r0, #0
movne sp, r0
movne r9, r0
#endif
#if ! defined(CONFIG_SPL_BUILD)
bl coloured_LED_init
bl red_led_on
#endif
mov r0, r9
ldr r1, [r9, #GD_RELOCADDR]
#if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
ldr lr, =board_init_r
bx lr
#else
ldr pc, =board_init_r
#endif
#endif
ENDPROC(_main)
Hàm board_init_f_alloc_reserve trong common/init/board_init.c:
ulong board_init_f_alloc_reserve(ulong top)
{
/* Reserve early malloc arena */
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
#endif
/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
top = rounddown(top-sizeof(struct global_data), 16);
return top;
}
Hàm này thực hiện:
- Dành riêng vùng nhớ early malloc
- Dành riêng vùngglobal_data (gd)
- Trả về địa chỉ top mới (16-byte aligned)
CONFIG_SYS_MALLOC_F_LEN=0x3000, sizeof(struct global_data)=240 (GD_SIZE).
Trong file arch/arm/include/asm/global_data.h, con trỏ gd được định nghĩa:
gd là con trỏ đến cấu trúc gd_t, lưu trong thanh ghi r9.
Cấu trúc gd_t trong include/asm-generic/global_data.h:
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned int baudrate;
unsigned long cpu_clk;
unsigned long bus_clk;
unsigned long pci_clk;
unsigned long mem_clk;
...
#if defined(CONFIG_TRANSLATION_OFFSET)
fdt_addr_t translation_offset;
#endif
#if CONFIG_IS_ENABLED(WDT)
struct udevice *watchdog_dev;
#endif
} gd_t;
Hàm board_init_f_init_reserve trong common/init/board_init.c:
void board_init_f_init_reserve(ulong base)
{
struct global_data *gd_ptr;
gd_ptr = (struct global_data *)base;
memset(gd_ptr, '\0', sizeof(*gd));
#if !defined(CONFIG_ARM)
arch_setup_gd(gd_ptr);
#endif
base += roundup(sizeof(struct global_data), 16);
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
gd->malloc_base = base;
base += CONFIG_VAL(SYS_MALLOC_F_LEN);
#endif
if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
board_init_f_init_stack_protection();
}
Hàm này khởi tạo gd (xóa zero) và thiết lập gd->malloc_base là địa chỉ base cộng với kích thước gd.
2.4 Hàm board_init_f
Hàm _main gọi hàm board_init_f, hàm này thực hiện hai nhiệm vụ chính:
- Khởi tạo các thiết bị ngoại vi như serial, timer, in thông báo...
- Khởi tạo các thành viên của gd. U-Boot sẽ tự di chuyển đến vùng nhớ cuối của DRAM để nhường chỗ cho Linux kernel. Trước khi di chuyển, cần cấp phát vị trí và kích thước cho các phần của U-Boot như gd, malloc pool... Thông tin này được lưu trong các thành viên của gd.
Hàm board_init_f được định nghĩa trong common/board_f.c:
void board_init_f(ulong boot_flags)
{
gd->flags = boot_flags;
gd->have_console = 0;
if (initcall_run_list(init_sequence_f))
hang();
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
!defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64) && \
!defined(CONFIG_ARC)
hang();
#endif
}
Mảng init_sequence_f cũng được định nghĩa trong common/board_f.c:
static const init_fnc_t init_sequence_f[] = {
setup_mon_len,
fdtdec_setup,
initf_malloc,
log_init,
initf_bootstage,
setup_spl_handoff,
initf_console_record,
arch_cpu_init,
mach_cpu_init,
initf_dm,
arch_cpu_init_dm,
get_clocks,
timer_init,
env_init,
init_baud_rate,
serial_init,
console_init_f,
display_options,
display_text_info,
#if defined(CONFIG_PPC) || defined(CONFIG_SH) || defined(CONFIG_X86)
checkcpu,
#endif
print_cpuinfo,
show_board_info,
INIT_FUNC_WATCHDOG_INIT
INIT_FUNC_WATCHDOG_RESET
init_func_vid,
announce_dram_init,
dram_init,
INIT_FUNC_WATCHDOG_RESET
testdram,
INIT_FUNC_WATCHDOG_RESET
init_post,
INIT_FUNC_WATCHDOG_RESET
setup_dest_addr,
reserve_pram,
reserve_round_4k,
reserve_mmu,
reserve_video,
reserve_trace,
reserve_uboot,
reserve_malloc,
reserve_board,
setup_machine,
reserve_global_data,
reserve_fdt,
reserve_bootstage,
reserve_bloblist,
reserve_arch,
reserve_stacks,
dram_init_banksize,
show_dram_config,
display_new_sp,
reloc_fdt,
reloc_bootstage,
reloc_bloblist,
setup_reloc,
NULL,
};
Giải thích một số hàm quan trọng:
- setup_mon_len: Thiết lập mon_len - kích thước toàn bộ code U-Boot
- fdtdec_setup: Thiết lập con trỏ fdt_blob chứa địa chỉ device tree
- initf_malloc: Khởi tạo các biến liên quan đến malloc trong gd
- serial_init: Khởi tạo serial, thiết lập cờ GD_FLG_SERIAL_READY
- console_init_f: Thiết lập gd->have_console = 1
- dram_init: Thiết lập gd->ram_size (kích thước RAM)
- setup_dest_addr: Thiết lập ram_base, ram_size, ram_top, relocaddr
- reserve_uboot: Dành riêng vùng nhớ cho U-Boot sau khi di chuyển
- reserve_malloc: Dành riêng vùng malloc và bộ nhớ MMU
- reserve_stacks: Dành riêng vùng stack
- setup_reloc: Thiết lập các thành viên khác của gd phục vụ cho việc di chuyển code
Sau khi board_init_f hoàn thành, bộ nhớ được phân bổ như sau:
2.5 Hàm relocate_code
Hàm relocate_code thực hiện sao chép code đến vị trí mới. Hàm này được định nghĩa trong arch/arm/lib/relocate.S.
Quy trình khởi động U-Boot tóm tắt:
- Từ điểm vào boot_start trong vectors.S
- Gọi reset_handler trong start.S
- Thiết lập chế độ SVC, vô hiệu hóa ngắt
- Cấu hình CP15, thiết lập vector table
- Gọi lowlevel_init để khởi tạo RAM
- Gọi _main để thiết lập môi trường C
- Gọi board_init_f để khởi tạo thiết bị ngoại vi và phân bổ bộ nhớ
- Gọi relocate_code để di chuyển code đến vị trí mới trong DRAM
- Gọi relocate_vectors để di chuyển vector table
- Gọi board_init_r để hoàn tất khởi động