U-Boot Practically Porting Guide
Author: Aaron Wong aaronwong@engineer.com
U-Boot的移植之(二)進階篇:從源代碼看系統啟動過程
為什麼要分析源代碼?分析優秀的源代碼本身就是一個學習的過程,也是進行深入研究的必經之路。不過在此我們的主要目的並非要研究U-boot或Bootloader技術本身,而僅僅是為了成功的並且恰當的將U-Boot移植到我們的開發板上。只有結合源代碼瞭解了U-boot的系統引導過程,才能在移植和調試過程中保持清晰的思路,才能在碰到困難和問題時從根本上加以解決。
在動手分析之前,至少應該對U-Boot的源代碼結構有基本的瞭解,很多參考書都有這方面的介紹,華清遠見的《嵌入式Linux系統開發技術詳解——基於ARM》的講解就比較清晰。
本文以lubbock開發板為例,以系統啟動的流程為線索進行縱向分析:後續的移植工作也將以此開發板為模板。Lubbock使用PXA255處理器。
首先要找到程序入口點。從board/lubbock/u-boot.lds可以發現,u-boot的程序入口為_start,在cpu/pxa/start.o當中。因此首先要分析start.S程序,U-Boot中所有的PXA系列的處理器都從這裡開始執行第一條語句。
-
.globl _start
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
0x0地址開始是ARM異常向量表,學過ARM體系結構與編程的都明白,非常簡單,不多廢話。一上電的第一條指令是跳轉到reset復位處理程序:
-
reset:
/* 進入SVC模式 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit /* we do sys-critical inits */
#endif
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:
......
一般不要定義CONFIG_SKIP_LOWLEVEL_INIT,因此,接下來跳轉到cpu_init_crit處開始執行:
-
cpu_init_crit:
/* 屏蔽所有中斷 */
/* 設置時鍾源,關閉除FFUART,SRAM,SDRAM,FLASH以外的外設時鍾 */
......
#ifdef CFG_CPUSPEED
ldr r0, CC_BASE /* 時鍾控制寄存器基址 */
ldr r1, cpuspeed
/* cpuspeed: .word CFG_CPUSPEED */
str r1, [r0, #CCCR]
mov r0, #2
mcr p14, 0, r0, c6, c0, 0
setspeed_done:
#endif /* CFG_CPUSPEED */
/* 跳轉到lowlevel_init,這裡ip即r12,用作暫存寄存器 */
mov ip, lr
bl lowlevel_init
mov lr, ip
/* Memory interfaces are working. Disable MMU and enable I-cache. */
ldr r0, =0x2001
......
/* 關閉MMU,使能I-Cache(可選) */
mov pc, lr /* 這裡是從cpu_init_crit返回到relocate標號 */
可見,在cpu_init_crit中的主要工作是設置時鍾,配置處理器主頻(這時CPU的工作頻率還沒有改變),調用lowlevel_init函數進行底層初始化(包括調整處理器工作頻率、系統總線頻率、存儲器時鍾頻率以及存儲系統的初始化等工作),隨後關閉MMU並使能I-Cache,再返回。
lowlevel_init函數在board/lubbock/lowlevel_init.S中定義,其流程都是按照PXA27X的開發手冊來的,所以不再贅述。僅指出,其中的寄存器在include/asm-arm/arch-pxa/pxa-regs.h頭文件中定義,寄存器初始化值在include/configs/lubbock.h中定義。另外,在後面的實際移植工作中,由於目標板XSBASE270使用的PXA270處理器,可使用adsvix開發板的lowlevel_init.S文件(lubbock中沒有開啟turbo模式)。
接著程序的執行線索進行分析。從cpu_init_crit返回後就開始relocate(重定位),即將U-boot從FLASH存儲器搬運到SDRAM中TEXT_BASE開始的存儲空間(TEXT_BASE在board/lubbock/config.mk中定義),並初始化堆棧(清零.bss段),以在SDRAM中開始進入到Bootloader stage 2的C程序入口。Relocate部分開始的代碼如下:
-
/* 之前已定義的部分變量有:
_TEXT_BASE: .word TEXT_BASE
_armboot_start: .word _start
_bss_start: .word __bss_start
_bss_end: .word _end */
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start /* 讀入_start到r2 */
ldr r3, _bss_start /* 讀入__bss_start到r3 */
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
/* Set up the stack */
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
ldr pc, _start_armboot
_start_armboot: .word start_armboot
這是很經典的一段代碼,相信學習凡是過ARM編程的,都分析過這段代碼,所以也不再贅述。之所以列出這段代碼,一是為了找到C程序入口start_armboot,二是為了給出U-Boot的一個存儲器映射圖:
這個圖可以幫助我們更好地理解後續的C語言代碼以及U-Boot對內存的分配與使用情況。
接下來進入到Bootloader Stage 2即C語言代碼部分,入口是start_armboot,對應的源文件是lib_arm/board.c,這一文件對所有的ARM處理器都是通用的,因此在移植的時候不用修改。相關源代碼如下:
-
DECLARE_GLOBAL_DATA_PTR;
/* 在include/asm-arm/global_data.h中定義的一個全局寄存器變量的聲明:
* #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
* 用於存放全局數據結構體gd_t的地址。
*/
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr;
char *s;
#ifndef CFG_NO_FLASH
ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
/* 本次移植暫不配置VFD和LCD,後面也將不考慮的部分略去 */
/* 初始化全局數據結構體指針gd */
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
....../* memset在lib_generic/string.c中定義*/
memset ((void*)gd, 0, sizeof (gd_t)); /*用0填充全局數據表*gd */
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t)); /*用0填充(初始化) *gd->bd */
monitor_flash_len = _bss_start - _armboot_start;
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang (); /* 打印錯誤信息並死鎖 */
}
}
#ifndef CFG_NO_FLASH
/* configure available FLASH banks */
size = flash_init (); /* drivers/cfi_flash.c或自定義 */
display_flash_config (size);
#endif /* CFG_NO_FLASH */
/*armboot_start is defined in the board-specific linker script*/
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
......
/* initialize environment */
env_relocate ();
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* MAC Address */
{
int i;
ulong reg;
char *s, *e;
char tmp[64];
i = getenv_r ("ethaddr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
}
devices_init (); /* get the devices list going. */
.......
jumptable_init ();
console_init_r (); /* fully init console as a device */
enable_interrupts (); /* enable exceptions */
/* Perform network card initialisation if necessary */
#if defined(CONFIG_DRIVER_SMC91111)| |defined (CONFIG_DRIVER_LAN91C96)
if (getenv ("ethaddr"))
smc_set_mac_addr(gd->bd->bi_enetaddr);
#endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if defined(CONFIG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL)
copy_filename (BootFile, s, sizeof (BootFile));
#endif
#ifdef BOARD_LATE_INIT
board_late_init ();
#endif
......
/*main_loop() can return to retry autoboot, if so just run it again.*/
for (;;) {
main_loop ();
}
}
gd_t是全局數據表類型,在include/asm-arm/global_data.h中定義如下:
-
/*
Keep it *SMALL* and remember to set CFG_GBL_DATA_SIZE > sizeof(gd_t)
*/
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /*Checksum of Environment valid?*/
unsigned long fb_base; /* base address of frame buffer */
.......
void **jt; /* jump table */
} gd_t;
其中,bd_t在include/asm-arm/u-boot.h中定義如下:
-
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */
unsigned long bi_ip_addr; /* IP Address */
unsigned char bi_enetaddr[6]; /* Ethernet adress */
struct environment_s *bi_env;
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params*/
struct /* RAM configuration */
{
ulong start;
ulong size;
}bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
jt是函數數組指針,隨後將在jumptable_init()函數中初始化。
從lib_arm/board.c的源碼不難分析出系統的啟動流程:首先初始化全局數據表,然後順序執行函數指針數組init_sequence中的一系列初始化函數——由其在本文件中的相關定義可得知初始化流程:
-
typedef int (init_fnc_t) (void);
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup -- cpu/pxa/cpu.c */
board_init, /* basic board setup --board/lubbock/lubbock.c */
interrupt_init, /* set up exceptions -- cpu/pxa/interrupts.c */
env_init, /* initialize environment -- common/env_flash.c */
init_baudrate, /* initialze baudrate settings--lib_arm/board.c */
serial_init, /* serial communications setup--cpu/pxa/serial.c */
console_init_f, /* stage 1 init of console -- common/console.c */
display_banner, /* say that we are here -- lib_arm/board.c */
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
dram_init, /* configure available RAM banks --board/lubbock/lubbock.c */
display_dram_config, /* lib_arm/board.c */
NULL,
};
在執行這個函數序列的過程中,任何一個函數異常返回都會導致u-boot「死鎖」或說「掛起」在hang()函數的死循環當中。
若一切順利,接下來就調用flash_init()函數初始化CFI FLASH(針對NOR型閃存而言),該函數在drivers/cfi_flash.c中定義,不過,只有在目標板頭文件中」#define CFG_FLASH_CFI_DRIVER」之後該驅動才會被編譯;在lubbock的u-boot實現當中,include/configs/lubbock.h中沒有定義CFG_FLASH_CFI_DRIVER,而是在board/lubbock/ flash.c中實現了自己的FLASH驅動,包括flash_init()在內。在移植U-Boot時,可以根據實際情況選擇使用U-Boot自帶的FLASH驅動還是自己編寫新的驅動。如果配置了NAND閃存,還會對其進行初始化;筆者的XSABSE270板沒有銲接NAND FLASH,故對此不作討論。
接下來調用env_relocate()函數初始化環境變量,該函數在common/env_common.c文件中定義。在同一文件中可以發現還定義了一個字符數組default_environment[],用於描述缺省的環境變量,這些都要在include/configs/lubbock.h頭文件中進行設置,包括啟動命令CONFIG_BOOTCOMMAND,波特率CONFIG_BAUDRATE,IP地址CONFIG_IPADDR等等。
然後是獲取自設置的目標板的網絡地址,包括IP地址和MAC地址。
再然後是調用common/devices.c中定義的devices_init()函數來創建設備列表,並初始化相應的設備,主要是」stdin」,」stdout」,」stderr」以及自定義的設備如I2C,LCD等。這些相關代碼是與平台無關的,因此從移植的角度考慮,不必作細緻的研究與分析。
接著調用common/exports.c中定義的jumptable_init()函數,初始化全局數據表中的跳轉表gd->jt,跳轉表是一個函數指針數組,定義了u-boot中基本的常用的函數庫;而gd->jt是這個函數指針數組的首指針。部分代碼如下:
-
void jumptable_init (void) {
int i;
gd->jt = (void **) malloc (XF_MAX * sizeof (void *));
for (i = 0; i < XF_MAX; i++)
gd->jt[i] = (void *) dummy;
gd->jt[XF_get_version] = (void *) get_version;
gd->jt[XF_malloc] = (void *) malloc;
gd->jt[XF_free] = (void *) free;
gd->jt[XF_getenv] = (void *) getenv;
gd->jt[XF_setenv] = (void *) setenv;
......
}
上面的XF_get_version, XF_malloc, XF_free等在include/exports.h的枚舉變量中定義,因此,實際上是作為」Label式整型序號」使用,即XF_get_version=1, XF_malloc=2, XF_free=3 ...,相關代碼如下:
-
enum { /* include/exports.h */
#define EXPORT_FUNC(x) XF_ ## x ,
#include <_exports.h>
#undef EXPORT_FUNC
XF_MAX
};
EXPORT_FUNC(get_version)
EXPORT_FUNC(getc)
EXPORT_FUNC(tstc)
EXPORT_FUNC(putc)
EXPORT_FUNC(puts)
EXPORT_FUNC(printf)
......... /* include/_exports.h */
由於這些也是平台無關的代碼,因此在移植過程中也不必深究。
然後是調用common/console.c中定義的函數console_init_r()初始化串口控制台,這同樣是平台無關的代碼,所以不必關心。
這時U-Boot的基本功能已經初始化完畢,便可開中斷,並進行附加功能的配置與初始化,包括網卡驅動配置,目標板使用LAN91C1111網卡,對應SMC91111網卡驅動,可以根據需要配置其他的網卡驅動如CS8900等,這些都在include/configs/lubbock.h中定義。
然後是調用board/lubbock/lubbock.c中定義的board_late_init()函數進行板級的後期初始化,實際上是配置stdout和stderr的硬件設備。相關源代碼如下:
-
int board_late_init(void)
{
setenv("stdout", "serial");
setenv("stderr", "serial");
return 0;
}
最後需要注意的一個很重要的文件是lib_arm/armlinux.c,它實現的功能包括設置內核啟動參數,並負責將這些參數傳遞給內核,最後跳轉到Linux內核入口函數,將控制權交給內核。
具體傳遞哪些參數,是通過在include/configs/lubbock.c中指定條件編譯選項來控制的,對應於lib_arm/armlinux.c中的部分源代碼形式如下:
-
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG)
static void setup_start_tag (bd_t *bd);
# ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd);
# endif
static void setup_commandline_tag (bd_t *bd, char *commandline);
# ifdef CONFIG_INITRD_TAG
static void setup_initrd_tag (bd_t *bd, ulong initrd_start,
ulong initrd_end);
# endif
static void setup_end_tag (bd_t *bd);
static struct tag *params;
#endif
......
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
......
void (*theKernel)(int zero, int arch, uint params);
......
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
......
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}
關於這個參數列表中各個參數的定義及含義,以及參數列表的初始化過程,可以參考Booting ARM Linux一文。內核是如何找到這個參數列表在內存中的位置,以接收這些參數的呢?實際上,參數列表(tag list)在內存中的起始地址會保存在通用寄存器
沒有留言:
張貼留言