uboot学习笔记(三)启动源码分析之第二阶段(c语言部分)
/**
* ../u-boot-2009.08\lib_arm
* called by cpu/arm926ejs/start.S
*uboot 第二阶段代码的开始。
*start_armboot函数是uboot从flash跳转到RAM执行的第一个函数
*/
void start_armboot (void)
{
init_fnc_t**init_fnc_ptr;
char*s;
#if defined(CONFIG_VFD) ||defined(CONFIG_LCD)
unsignedlong addr;
#endif
/*gd指向uboot全局环境变量配置结构体,*/
/*Pointer is writable since we allocated a register for it */
gd= (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
/*内存屏障,这句话告诉编译器,不要优化代码*/
/*告诉编译器寄存器和cache缓存的值都无效了,需要重新访问内存*/
/*它会把当前寄存器的值重新回写(同步)到内存,需要的时候再从内存读出*/
/*"":: :"memory" 汇编指令,此处表示空语句*/
/*Memory描述符告知GCC:
1)不要将该段内嵌汇编指令与前面的指令重新排序;也就是在执行内嵌汇编代码之前,它前面的指令都执行完毕
2)不要将变量缓存到寄存器,因为这段代码可能会用到内存变量,而这些内存变量会以不可预知的方式发生改变,
因此GCC插入必要的代码先将缓存到寄存器的变量值写回内存,如果后面又访问这些变量,需要重新访问内存。
如果汇编指令修改了内存,但是GCC 本身却察觉不到,因为在输出部分没有描述,
此时就需要在修改描述部分增加"memory",告诉GCC 内存已经被修改,GCC 得知这个信息后,
就会在这段指令之前,插入必要的指令将前面因为优化Cache 到寄存器中的变量值先写回内存,
如果以后又要使用这些变量再重新读取。
使用"volatile"也可以达到这个目的,但是我们在每个变量前增加该关键字,不如使用"memory"方便*/
/*compiler optimization barrier needed for GCC >= 3.4 */
__asm____volatile__("": : :"memory"); /*内存屏障*/
/*上面语句是为了确保下面语句能成功把内存清零*/
/*确保之前的指令都执行完毕,防止缓存对内存的意外修改?确保原子性?*/
memset((void*)gd, 0, sizeof (gd_t));
gd->bd= (bd_t*)((char*)gd - sizeof(bd_t));
memset(gd->bd, 0, sizeof (bd_t));
gd->flags|= GD_FLG_RELOC;//标记表示已经重定向到内存
/*uboot的 code + data的大小*/
monitor_flash_len= _bss_start - _armboot_start;
/*遍历init_sequence数组的函数指针,执行硬件初始化函数*/
/*初始化硬件包含cpu,timer,interrput,console,dram,i2c...*/
for(init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if((*init_fnc_ptr)() != 0) {
hang(); //失败处理
}
}
/*清malloc堆区,为malloc库函数做准备,不然malloc函数不能用*/
/*armboot_start is defined in the board-specific linker script */
mem_malloc_init(_armboot_start - CONFIG_SYS_MALLOC_LEN);
#ifndef CONFIG_SYS_NO_FLASH
/*configure available FLASH banks */
display_flash_config(flash_init ());
#endif /* CONFIG_SYS_NO_FLASH */
/*初始化显示缓存*/
#ifdef CONFIG_VFD
# ifndefPAGE_SIZE
# define PAGE_SIZE 4096
# endif
/*
* reserve memory for VFD display (always fullpages)
*/
/*bss_end is defined in the board-specific linker script */
addr= (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
vfd_setmem(addr);
gd->fb_base= addr;
#endif /* CONFIG_VFD */
#ifdef CONFIG_LCD
/*board init may have inited fb_base */
if(!gd->fb_base) {
# ifndefPAGE_SIZE
#define PAGE_SIZE 4096
# endif
/*
* reserve memory for LCD display (always fullpages)
*/
/*bss_end is defined in the board-specific linker script */
addr= (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
lcd_setmem(addr);
gd->fb_base= addr;
}
#endif /* CONFIG_LCD */
/*nand初始化*/
#if defined(CONFIG_CMD_NAND)
puts("NAND: ");
nand_init(); /* go init the NAND */
#endif
#if defined(CONFIG_CMD_ONENAND)
onenand_init();
#endif
#ifdef CONFIG_HAS_DATAFLASH
AT91F_DataflashInit();
dataflash_print_info();
#endif
/*sd 卡支持*/
#ifdef CONFIG_GENERIC_MMC
puts("MMC: ");
mmc_initialize(gd->bd);
#endif
/*重定位环境变量把flash中保存的环境变量拷贝到ram的堆区,并赋值给gd->env_addr地址*/
/*见common/env_common.c 下的void env_relocate (void)*/
/*见common/env_flash.c下的void env_relocate_spec (void)*/
/*initialize environment */
env_relocate();
#ifdef CONFIG_VFD
/*must do this after the framebuffer is allocated */
drv_vfd_init();
#endif /* CONFIG_VFD */
#ifdef CONFIG_SERIAL_MULTI
serial_initialize();
#endif
//ddr2信息
getddr2_information();
/*IP Address */
gd->bd->bi_ip_addr= getenv_IPaddr ("ipaddr");
stdio_init(); /* get the devices list going. */
jumptable_init();
#if defined(CONFIG_API)
/*Initialize API */
api_init();
#endif
/*初始化终端 as a device*/
console_init_r(); /* fully init console as a device*/
#if defined(CONFIG_ARCH_MISC_INIT)
/*miscellaneous arch dependent initialisations */
arch_misc_init();
#endif
#if defined(CONFIG_MISC_INIT_R)
/*miscellaneous platform dependent initialisations */
misc_init_r();
#endif
/*第一阶段汇编代码关闭了中断,现在打开*/
/*enable exceptions */
enable_interrupts();
/*Perform network card initialisation if necessary */
#ifdef CONFIG_DRIVER_TI_EMAC
/*XXX: this needs to be moved to board init */
extern void davinci_eth_set_mac_addr (constu_int8_t *addr);
if(getenv ("ethaddr")) {
ucharenetaddr[6];
eth_getenv_enetaddr("ethaddr",enetaddr);
davinci_eth_set_mac_addr(enetaddr);
}
#endif
#ifdef CONFIG_DRIVER_CS8900
/*XXX: this needs to be moved to board init */
cs8900_get_enetaddr();
#endif
#if defined(CONFIG_DRIVER_SMC91111) ||defined (CONFIG_DRIVER_LAN91C96)
/*XXX: this needs to be moved to board init */
if(getenv ("ethaddr")) {
ucharenetaddr[6];
eth_getenv_enetaddr("ethaddr",enetaddr);
smc_set_mac_addr(enetaddr);
}
#endif /* CONFIG_DRIVER_SMC91111 ||CONFIG_DRIVER_LAN91C96 */
#if defined(CONFIG_ENC28J60_ETH) &&!defined(CONFIG_ETHADDR)
externvoid enc_set_mac_addr (void);
enc_set_mac_addr();
#endif /* CONFIG_ENC28J60_ETH &&!CONFIG_ETHADDR*/
/*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
#ifdef CONFIG_ANDROID_RECOVERY
check_recovery_mode();
#endif
#if defined(CONFIG_CMD_NET)
#if defined(CONFIG_NET_MULTI)
puts("Net: ");
#endif
eth_initialize(gd->bd);
#if defined(CONFIG_RESET_PHY_R)
debug("Reset Ethernet PHY\n");
reset_phy();
#endif
#endif
/*初始化到此结束,死循环与用户交互界面common/main.c */
/*main_loop() can return to retry autoboot, if so just run it again. */
for(;;) {
//主循环main_loop 实现 bootdelay延时时间到之前无输入,则加载bootcmd进入自启动模式;如果有输入则进入交互模式
main_loop();
}
/*NOTREACHED - no way out of command loop except booting */
}
void main_loop (void)
{
#if defined(CONFIG_BOOTDELAY) &&(CONFIG_BOOTDELAY >= 0)
s= getenv ("bootdelay");
bootdelay= s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
debug("### main_loop entered: bootdelay=%d\n\n", bootdelay);
# ifdef CONFIG_BOOT_RETRY_TIME
init_cmd_timeout();
# endif /*CONFIG_BOOT_RETRY_TIME */
//获取自启动命令如 bootcmd = tftp 20008000zImage ; go zImage 表示从网络加载内核运行,否则进入交互模式
s= getenv ("bootcmd");
debug("### main_loop: bootcmd=\"%s\"\n", s ? s :"");
//如果延时大于等于零,并且没有在延时过程中接收到按键,则引导内核。进入自启动模式
/*abortboot做倒计时功能*/
if(bootdelay >= 0 && s && !abortboot (bootdelay)) {
# ifdef CONFIG_AUTOBOOT_KEYED
intprev = disable_ctrlc(1); /* disableControl C checking */
# endif
# ifndef CONFIG_SYS_HUSH_PARSER
run_command(s, 0); //执行命令s
# else
parse_string_outer(s,FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
……
}
/*
* Main Loopfor Monitor Command Processing
*/
For(;;){
……;
}
}
U_BOOT_CMD(
bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
"bootapplication image from memory",
"[addr[arg ...]]\n - boot application imagestored in memory\n"
"\tpassingarguments 'arg ...'; when booting a Linux kernel,\n"
"\t'arg'can be the address of an initrd image\n"
}
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc,char *argv[])
{
ulong iflag;
ulong load_end = 0;
int ret;
boot_os_fn *boot_fn;
/*relocate boot function table */
if(!relocated) {
inti;
for(i = 0; i < ARRAY_SIZE(boot_os); i++)
if(boot_os[i] != NULL)
boot_os[i]+= gd->reloc_off;
relocated= 1;
}
/*determine if we have a sub command */
if(argc > 1) {
char*endp;
simple_strtoul(argv[1],&endp, 16);
/*endp pointing to NULL means that argv[1] was just a
* valid number, pass it along to the normalbootm processing
*
* If endp is ':' or '#' assume a FITidentifier so pass
* along for normal processing.
*
* Right now we assume the first arg shouldnever be '-'
*/
if((*endp != 0) && (*endp != ':') && (*endp != '#'))
returndo_bootm_subcommand(cmdtp, flag, argc, argv);
}
//用于获取内核头信息,起始地址和长度。
/*bootm_start会调用boot_get_kernel函数找内核映象文件的起始地址load_addr的头信息,然后解析。* 内核地址定义在一个宏:ulong load_addr =CONFIG_SYS_LOAD_ADDR; /* Default Load Address */
if(bootm_start(cmdtp, flag, argc, argv))
return1;
/*
* We have reached the point of no return: weare going to
* overwrite all exception vector code, so wecannot easily
* recover from any failures any more...
*/
iflag= disable_interrupts();
/*搬移内核到内存*/
/*从image_start拷贝到load地址*/
ret= bootm_load_os(images.os, &load_end, 1);
……
#ifdef CONFIG_SILENT_CONSOLE
if(images.os.os == IH_OS_LINUX)
fixup_silent_linux();
#endif
/*根据系统类型找boot回调函数*/
boot_fn= boot_os[images.os.os];
……
/*这里实际上是调用do_bootm_linux函数启动内核*/
boot_fn(0,argc, argv, &images);
do_reset(cmdtp, flag, argc, argv);
return1;
}
二、添加自己的UBOOT命令
typedefstruct cmd_tbl_s cmd_tbl_t;
//定义 section 属性的结构体。编译的时候会单独生成一个名为.u_boot_cmd 的 section 段
#defineStruct_Section __attribute__ ((unused,section (".u_boot_cmd")))
//这个宏定义一个命令结构体变量。并用name,maxargs,rep,cmd,usage,help 初始化各个域。。。 。
#defineU_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name
Struct_Section= {#name, maxargs, rep, cmd, usage, help}
在 u-boot 如何添加一个命令?
1) CFG_CMD_xxx 命令选项位标志 在include/cmd_confdefs.h 中定义 每个板子的配置文件 如include/config/smdk2410.h) 中都可以定义 u-boot 需要的命令,如果要添加一个命令 ,必须添加相应的命令选项如下:
#define CONFIG_COMMANDS /
(CONFIG_CMD_DFL | /
CFG_CMD_CACHE | /
/*CFG_CMD_NAND |*/ /
/*CFG_CMD_EEPROM |*/ /
/*CFG_CMD_I2C|*/ /
/*CFG_CMD_USB |*//
CFG_CMD_REGINFO | /
CFG_CMD_DATE | /
CFG_CMD_ELF)
定义这个选项主要是为了编译命令需要的源文件 ,大部分命令都在都在common 文件夹下对应一个源文件 cmd_*.c
例如: cmd_cache.c 实现 cache 命令 文件开头就有一行编译条件:
#if(CONFIG_COMMANDS&CFG_CMD_CACHE)
也就是说 如果配置头文件中CONFIG_COMMANDS 不或上相应命令的选项这里就不会被编译 。
2)定义命令结构体变量 如 :
U_BOOT_CMD
(dcache, 2, 1, do_dcache,
"dcache - enable or disable data cache/n",
"[on,off]/n"
" - enable or disable data (writethrough)cache/n"
);
其实就是定义了一个 cmd_tbl_t 类型的结构体变量,这个结构体变量名为 __u_boot_cmd_dcache。 其中变量的五个域初始化为括号的内容。分别指明了命令名 参数个数 重复数 ,执行命令的函数命令提示每个命令都对应这样一个变量 ,同时这个结构体变量的section 属性为.u_boot_cmd.也就是说每个变量编译结束在目标文件中都会有一个.u_boot_cmd 的 section.一个 section 是连接时的一个输入段 ,如.text,.bss,.data等都是 section 名 最后由链接程序把所有的.u_boot_cmd段连接在一起 ,这样就组成了一个命令结构体数组 u-boot.lds 中相应脚本如下:. = .; __u_boot_cmd_start = .; .u_boot_cmd : { *(.u_boot_cmd) }__u_boot_cmd_end = .; 可以看到所有的命令结构体变量集中在__u_boot_cmd_start 开始到__u_boot_cmd_end 结束的连续地址范围内 这样形成一个cmd_tbl_t 类型的数组 ,run_command函数就是在这个数组中查找命令的 函数并执行
3)实现命令处理函数 。
命令处理函数的格式: void function (cmd_tbl_t*cmdtp, int flag, int argc, char *argv[])
总体来说,如果要实现自己的命令,应该在应该在 include/com_confdefs.h中定义一个命令选项标志位。在板子的配置文件中添加命令自己的选项。按照 u-boot的风格,可以在 common/下面添加自己的 cmd_*.c,并且定义自己的命令结构体变量,如 U_BOOT_CMD( mycommand, 2, 1,do_mycommand, "my command!/n", ".../n" " ../n" );然后实现自己的命令处理函数 do_mycommand(cmd_tbl_t *cmdtp,int flag, int argc, char *argv[])。
三、UBOOT重要结构体
UBOOT重要结构体
/*==========uboot结构体 gd和 bd===================*/
//在gd中部署bd空间
//--include/asm-arm.u/u-boot.h
typedef struct bd_info { /*板级配置信息结构体 bd*/
int bi_baudrate; /* 串口通讯波特率 */
unsignedlong bi_ip_addr; /* 板子自己IP 地址 对应环境变量 ipaddr*/
structenvironment_s *bi_env; /* 环境变量开始地址 */
ulong bi_arch_number; /* 开发板的机器码 */
ulong bi_boot_params; /* 内核参数的开始地址 */ 见board/samsung/fsc100/fsc100.c 设置它的开发板的机器码和内核启动参数地址=PHYS_SDRAM_1+0x100=0x20000000+0x100 见include/configs/fsc100.h
//在传递参数到内核时采用了param_struct方式: 见common/cmd_boot.c go命令的优化(xx自己改的)
struct /* RAM配置信息 */
{
ulong start;
ulong size;
}bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t
typedef struct global_data { /*gd结构体*/
bd_t *bd;
unsignedlong flags; /*重定位标志*/
unsignedlong baudrate; /*波特率*/
unsignedlong have_console; /* serial_init() was called */
unsignedlong env_addr; /* 环境参数结构体地址 */
unsignedlong env_valid; /* 环境参数CRC效验 */
unsignedlong fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsignedchar vfd_type; /* display type */
#endif
#ifdef CONFIG_FSL_ESDHC
unsignedlong sdhc_clk;
#endif
#if 0
unsignedlong cpu_clk; /* CPU clock in Hz! */
unsignedlong bus_clk;
phys_size_t ram_size; /*RAM size */
unsignedlong reset_status; /* reset status register at boot */
#endif
void **jt; /*jump table 跳转表,函数调用登记*/
} gd_t;/*include/asm-arm/global.h*/