.globl _start _start: b reset ldr pc,_undefined_instruction 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,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. */ ...... /* 关闭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 */ /* 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,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] */ /* until source end addreee [r2] */ ble copy_loop
/* Set up the stack */ stack_setup: /* upper 128 KiB: relocated uboot */ sub r0,#CFG_MALLOC_LEN /* malloc area */ /* bdinfo */ #ifdef CONFIG_USE_IRQ #endif sub sp,#12 /* leave 3 words for abort-stack */
clear_bss: /* find start of bss segment */ /* stop here */ mov r2,#0x00000000 /* clear */
clbss_l:str r2,[r0] /* clear loop... */ add r0,#4 ble clbss_l
_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; #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, sizeof (gd_t)); /*用0填充全局数据表*gd */ gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); memset (gd->bd,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 (); /* 打印错误信息并死锁 } }
/* 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)); #ifdef BOARD_LATE_INIT board_late_init (); ...... /*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 */ 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) { 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",serif">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,serif">ulong initrd_end); 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)在内存中的起始地址会保存在通用寄存器R2中,并传递给内核。而按照习惯或说惯例,通常tag list的首地址(物理地址)会设置为RAM起始地址+ 0x100偏移量,因此R2的值实际上是确定不变的。另外,还要正确设置R0和R1的值,在呼叫内核时,R0的值应为0,R1中则应保存机器类型(machine type)编号。R0,R1和R2都会作为参数传递给内核。
在上面的代码中,定义了一个函数指针theKernel,通过倒数第二条语句将内核入口地址赋给theKernel(hdr是include/image.h中定义的一个image_header结构体类型的数据,hdr->ih_ep中保存了内核入口地址,ntohl的功能是字节顺序的大小端转换,相关代码可以参考tools/mkimage.c),最后,根据APCS规则,将0,bd->bi_boot_params 依次作为参数通过R0,R1和R2传递给theKernel函数,并进入内核启动部分。
至此,我们已经从源代码入手简要分析了U-Boot的启动流程,在这个过程中,我们对前一篇文章“添加新的目标板定义”也有了更进一步的理解和认识:为什么要添加这些文件;哪些文件是平台相关的并且必须要根据平台特性进行修改的;哪些文件是平台无关的,是不需要修改的,只需在头文件中作适当配置即可。
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|
| |