Uboot32之start.S第二阶段part4
00818: devices_init (); /* get the devices list going. */
---------------------------------- devices_init函数解析----------------------------------------------------------------------
00162: int devices_init (void)
00163: {
00164: #ifndef CONFIG_ARM
00164: /* already relocated for current ARM implementation */
00165: ulong relocation_offset = gd->reloc_off;
gd->relocaaddr为目标addr,gd->start_addr_sp为目标addr_sp,gd->reloc_off为目标addr和现在实际code起始地址的偏移。reloc_off非常重要,会作为后面relocate_code函数的参数,来实现code的拷贝。
00166: int i;
#define MAX_FILES 3
char *stdio_names[MAX_FILES] = { "stdin", "stdout", "stderr" };
00168: /* relocate device name pointers */
00169: for (i = 0; i < (sizeof (stdio_names) / sizeof (char *)); ++i)//指针数组的元素个数,有三个指针元素
{
00170: stdio_names[i] = (char *) (((ulong) stdio_names[i]) +relocation_offset);
////重定位设备输入输出设备名字指针
00172: }
00173: #endif
00174:
00175: /* Initialize the list */
00176: devlist = ListCreate (sizeof (device_t)); ////初始化列表,一共52个字节
上述就是简单的创建链表,也就是链表的初始化。
00178: if (devlist == NULL) {
00179: eputs ("Cannot initialize the list of devices!\n");
判断这个链表是不是创建成功,如果失败则返回。
00180: return -1;
00181: }
00182: drv_system_init ();
------------- drv_system_init函数解析--------
00071: static void drv_system_init (void)
00072: {
00073: device_t dev; //创建一个设备链表节点
00075: memset (&dev, 0, sizeof (dev));清零。
00077: strcpy (dev.name, "serial");//将设备链表的节点名字定义为串口
00078: dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT |
00078: DEV_FLAGS_SYSTEM;
00085: dev.putc = serial_putc;
00086: dev.puts = serial_puts;
00087: dev.getc = serial_getc;
00088: dev.tstc = serial_tstc;
00091: device_register (&dev);//注册串口设备的链表,将链表添加到设备链表中
00093: } ? end drv_system_init ?
这个函数主要进行串口设备增加,并将串口设备增加至我们的设备链表中。
-------------- drv_system_init函数解析结束-----
00184: return (0);
00185: } ? end devices_init ?
1)devices_init看名字就是设备的初始化。这里的设备指的就是开发板上的硬件设备。放在这里初始化的设备都是驱动设备,这个函数本来就是从驱动框架中衍生出来的。uboot中很多设备的驱动是直接移植linux内核的(譬如网卡、SD卡),linux内核中的驱动都有相应的设备初始化函数。linux内核在启动过程中就有一个devices_init(名字不一定完全对,但是差不多),作用就是集中执行各种硬件驱动的init函数。
2)uboot的这个函数其实就是从linux内核中移植过来的,它的作用也是去执行所有的从linux内核中继承来的那些硬件驱动的初始化函数。
---------------------------------- devices_init函数解析结束------------------------------------------------------
00823:
00824: jumptable_init ();
1)jumptable跳转表,本身是一个函数指针数组,里面记录了很多函数的函数名。看这阵势是要实现一个函数指针到具体函数的映射关系,将来通过跳转表中的函数指针就可以执行具体的函数。这个其实就是在用C语言实现面向对象编程。在linux内核中有很多这种技巧。
2)通过分析发现跳转表只是被赋值从未被引用,因此跳转表在uboot中根本就没使用。
00825: #if !defined(CONFIG_SMDK6442)
00826: console_init_r ();
---------------------------------- console_init_r函数解析------------------------------------------------------
00487: Called after the relocation - use desired console functions */
00488: int console_init_r (void)
00489: {
00490: device_t *inputdev = NULL, *outputdev = NULL;//定义一个设备节点,等待注册
00491: int i, items = ListNumItems (devlist);//取得设备链中的设备数
00492: /* Scan devices looking for input and output devices */
00493: for (i = 1; (i <= items) && ((inputdev == NULL) || (outputdev == NULL)); i++)
{
00497: device_t *dev = ListGetPtrToItem (devlist, i);
在设备链中按注册的顺序查找输入输出设备,在设备注册时 dev.flags表示此设备的类型。
00498:
00499: if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL))
{
00500: inputdev = dev;
00501: }
00502: if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL))
{
00503: outputdev = dev;
00504: }
00505: }
比如这里drv_system_init,此设备是第一个注册的设备,且其dev.flags为// DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM 所以上面过后,输入输出设备都指定为// drv_system_init里注册的设备了
00507: /* Initializes output console first */
00508: if (outputdev != NULL)
{
console_setfile (stdout, outputdev);
00510: console_setfile (stderr, outputdev);
将标准出错定为输出设备,这样有错误信息就会通过输出设备打印出来了
00511: }
00513: /* Initializes input console */
00514: if (inputdev != NULL) {console_setfile (stdin, inputdev); }
console_setfile做如下几件事:
1. 如果初始化该设备时注册了device_t.start,即启动设备的函数,则运行该函数,开启该设备
2. 将设备的指针存入stdio_devices[file],这应该是标准输入、标准输出、标准出错。
00518: gd->flags |= GD_FLG_DEVINIT; 初始化完成,加上标志位。
00518: /* device initialization completed */
00520: #ifndef CFG_CONSOLE_INFO_QUIET
00521: /* Print information */
00522: puts ("In: ");
00523: if (stdio_devices[stdin] == NULL) {
00524: puts ("No input devices available!\n");
00525: } else {
00526: printf ("%s\n", stdio_devices[stdin]->name);
00527: }
00528:
00529: puts ("Out: ");
00530: if (stdio_devices[stdout] == NULL) {
00531: puts ("No output devices available!\n");
00532: } else {
00533: printf ("%s\n", stdio_devices[stdout]->name);
00534: }
00535:
00536: puts ("Err: ");
00537: if (stdio_devices[stderr] == NULL) {
00538: puts ("No error devices available!\n");
00539: } else {
00540: printf ("%s\n", stdio_devices[stderr]->name);
00541: }
00542: #endif /* CFG_CONSOLE_INFO_QUIET */
这一段就是将信息打印出来,这里打印出出的信息就为:
00544: #ifndef CONFIG_X210
00545: /* Setting environment variables */
00546: for (i = 0; i < 3; i++) {
00547: setenv (stdio_names[i], stdio_devices[i]->name);
00548: }
00549: #endif
将信息写到环境变量中去char *stdio_names[MAX_FILES] = { "stdin", "stdout", "stderr" };
这样环境变量里 stdin stdout stderr 都为serial
00557: return (0);
00558: } ? end console_init_r ?
1)console_init_f是控制台的第一阶段初始化,console_init_r是第二阶段初始化。实际上第一阶段初始化并没有实质性工作,第二阶段初始化才进行了实质性工作。
2)uboot中有很多同名函数,使用SI工具去索引时经常索引到不对的函数处(回忆下当时start.S中找lowlevel_init.S时,自动索引找到的是错误的,真正的反而根本没找到。)
3)console_init_r就是console的纯软件架构方面的初始化(说白了就是去给console相关的数据结构中填充相应的值),所以属于纯软件配置类型的初始化。
4)uboot的console实际上并没有干有意义的转化,它就是直接调用的串口通信的函数。所以用不用console实际并没有什么分别。(在linux内核中console就可以提供缓冲机制等不用console不能实现的东西)。
---------------------------------- console_init_r函数解析结束------------------------------------------------------
00826: /* fully init console as a device */
00827: #endif
00834: /* enable exceptions */
00835: enable_interrupts ();
1)看名字应该是中断初始化代码。这里指的是CPSR中总中断标志位的使能。
2)因为我们uboot中没有使用中断,因此没有定义CONFIG_USE_IRQ宏,因此我们这里这个函数是个空壳子。
3)uboot中经常出现一种情况就是根据一个宏是否定义了来条件编译决定是否调用一个函数内部的代码。uboot中有2种解决方案来处理这种情况:方案一:在调用函数处使用条件编译,然后函数体实际完全提供代码。方案二:在调用函数处直接调用,然后在函数体处提供2个函数体,一个是有实体的一个是空壳子,用宏定义条件编译来决定实际编译时编译哪个函数进去。
00848: if ((s = getenv ("loadaddr")) != NULL) {
00849: load_addr = simple_strtoul (s, NULL, 16);
00850: }
uboot bootm在寻找OS镜像的时候会根据load_addr变量去对应的ram地址中找。
00851: #if defined(CONFIG_CMD_NET)
00852: if ((s = getenv ("bootfile")) != NULL) {
00853: copy_filename (BootFile, s, sizeof (BootFile));
00854: }
00855: #endif
这两个环境变量都是内核启动有关的,在启动linux内核时会参考这两个环境变量的值。
00857: #ifdef BOARD_LATE_INIT
00858: board_late_init ();
1)看名字这个函数就是开发板级别的一些初始化里比较晚的了,就是晚期初始化。所以晚期就是前面该初始化的都初始化过了,剩下的一些必须放在后面初始化的就在这里了。侧面说明了开发板级别的硬件软件初始化告一段落了。
2)对于X210来说,这个函数是空的。
00859: #endif
00860: #if defined(CONFIG_CMD_NET)
00861: #if defined(CONFIG_NET_MULTI)//没定义
00862: puts ("Net: ");
00863: #endif
00864: eth_initialize(gd->bd);
1)看名字应该是网卡相关的初始化。这里不是SoC与网卡芯片连接时SoC这边的初始化,而是网卡芯片本身的一些初始化。
2)对于X210(DM9000)来说,这个函数是空的。X210的网卡初始化在board_init函数中,网卡芯片的初始化在驱动中。
00869: #endif
00875:
00876: /****************lxg added**************/
00877: #ifdef CONFIG_MPAD
00878: extern int x210_preboot_init(void);
00879: x210_preboot_init();
00880: #endif
---------------------------------- mpadfb_init函数解析------------------------------------------------------
00834: void mpadfb_init()
00835: {;
00838: fb_init(); //获取时钟,填充结构体参数,初始化
00839: lcd_port_init(); //GPIO的初始化
00840: lcd_reg_init(); //控制寄存器的配置
00841: #ifdef CONFIG_CHECK_X210CV3
00842: init_logo(); //选择打印哪种logo
00843: #endif
00844: display_logo(&s5pv210_fb);
00845: #if(DISP_MODE == TRULY043)
00846: backlight_brigness_init(0);//背光的初始化
00847: #else//AT070TN92
00848: backlight_brigness_init(1);
00849: #endif
00864: #if(DISP_MODE == TRULY043)
00865: writel((readl(GPF3DAT) & ~(0x1<<5)) | (0x1<<5),GPF3DAT); 写一个背光
00866: #endif
00867: } ? end mpadfb_init ?
//这段代码主要就是初始化LCD和LOGO显示
---------------------------------- mpadfb_init函数解析结束------------------------------------------------------
00881: /****************end**********************/
00882:
00883: /* check menukey to update from sd */
00884: extern void update_all(void);
00885: if(check_menu_update_from_sd()==0)//update mode
00886: {
00887: puts ("[LEFT DOWN] update mode\n");
00888: run_command("fdisk -c 0",0);
00889: update_all();
00890: }
1)uboot启动的最后阶段设计了一个自动更新的功能。就是:我们可以将要升级的镜像放到SD卡的固定目录中,然后开机时在uboot启动的最后阶段检查升级标志(是一个按键。按键中标志为"LEFT"的那个按键,这个按键如果按下则表示update mode,如果启动时未按下则表示boot mode)。如果进入update mode则uboot会自动从SD卡中读取镜像文件然后烧录到iNand中;如果进入boot mode则uboot不执行update,直接启动正常运行。
2)这种机制能够帮助我们快速烧录系统,常用于量产时用SD卡进行系统烧录部署。
00891: else
00892: puts ("[LEFT UP] boot mode\n"); //boot模式就是我们正常的启动模式
main_loop() can return to retry autoboot, if so just run it again
00895: for (;;) {
00896: main_loop ();
---------------------------------- main_loop函数解析------------------------------------------------------
00271: void main_loop (void)
00272: {
00273: #ifndef CFG_HUSH_PARSER /采用"哈希表"方式的主循环
- uboot正常启动后,会调用main_loop(void)函数,进入main_loop()之后,如果在规定的时间(CONFIG_BOOTDELAY)内,没有检查到任何按键事件的发生,就会去加载OS,并启动系统,比如把linux内核压缩镜像从nand flash中读到sdram ,然后执行它。如果在CONFIG_BOOTDELAY时间内,用户按下键盘上的任意一个按键,uboot就会进入与用户交互的状态。如果用户在配置文件中定义了CONFIG_SYS_HUSH_PARSER,就会通过parse_file_outer(),去接收并解析用户命令,否则进入一个for(;;)循环中,通过 readline (CONFIG_SYS_PROMPT)接收用户命令,然后调用run_command(cmd,flag)去解析并执行命令。
如果定义了CONFIG_SYS_HUSH_PARSER,命令接收和解析讲采用busybox 中的hush(对应hush.c)工具来实现,与uboot原始的命令解析方法相比,该工具更加智能。这里主要讲uboot中基于hush的命令解析流程。不过hush的实现太过复杂 ,鉴于自己水平太次,只是简单追踪下流程。
当在配置文件中定义了CONFIG_SYS_HUSH_PARSER,main_loop会调用parse_file_outer(),进入hush,然后里面是一大堆和hush相关的机制,暂时不做分析,最终会调用到hush中的run_pipe_real(struct pipe *pi),在该函数中经过一些列解析 ,最终会调用到对应的命令执行函数。
2.uboot main_loop命令的解析方法有两种:
第一种就是最笨的办法,直接到.u_boot_cmd段中进行字符串比较,找到相同的那么就执行,如果命令比较少,相对来说对性能没有太大的损失,但是当命令很多时这种方法就不是很合适了。Uboot使用另外一种查找办法解决这个问题:使用hush表,只要用户在configs/MPC8349ADS.h文件中定义:
#define CFG_HUSH_PARSER
就可以实现了。这种方法与上面的方法基本相同,只是在查找方式上做了优化,这样可以提高查找速度,具体的代码可以查看common/main.c文件
00274: static char lastcommand[CFG_CBSIZE] = { 0, };
00275: int len;
00276: int rc = 1;
00277: int flag;
00278: #endif
我们使用了哈希表,因此上面代码不执行。
00280: #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >=0)
00281: char *s;
00282: int bootdelay;
00283: #endif
00336: #ifdef CFG_HUSH_PARSER
00337: u_boot_hush_start ();//哈希表的初始化
00338: #endif
00339:
00340: #ifdef CONFIG_AUTO_COMPLETE
00341: install_auto_complete();//自动补全
00342: #endif
00343:
00344: #ifdef CONFIG_FASTBOOT
00345: if (fastboot_preboot())
00346: run_command("fastboot", 0);
00347: #endif
00348:
00368: #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >=0)
00369: s = getenv ("bootdelay");
00370: bootdelay = s ? (int)simple_strtol(s, NULL, 10) :CONFIG_BOOTDELAY;
00393: s = getenv ("bootcmd");
00397: if (bootdelay >= 0 && s && !abortboot (bootdelay))
00397: {
00402: #ifndef CFG_HUSH_PARSER
00403: run_command (s, 0);
00404: #else
00405: parse_string_outer(s, FLAG_PARSE_SEMICOLON |FLAG_EXIT_FROM_LOOP);
00407: #endif
00412: }
00427: #endif /* CONFIG_BOOTDELAY */
00436: /*
00437: * Main Loop for Monitor Command Processing
00438: */
00439: #ifdef CFG_HUSH_PARSER
00440: parse_file_outer();
00441: /* This point is never reached */
00442: for (;;);
00443: #else
00444: for (;;)
{
00453: len = readline (CFG_PROMPT);
00454:
00455: flag = 0; /* assume no special flags for now */
00456: if (len > 0)
00457: strcpy (lastcommand, console_buffer);
00458: else if (len == 0)
00459: flag |= CMD_FLAG_REPEAT;
00475: if (len == -1)
00476: puts ("<INTERRUPT>\n");
00477: else
00478: rc = run_command (lastcommand, flag);
00479:
00480: if (rc <= 0) {
00481: /* invalid command or not repeatable, forget it */
00482: lastcommand[0] = 0;
00483: }
00484: } ? end for ;; ?
00485: #endif /*CFG_HUSH_PARSER*/
00486: } ? end main_loop ?
Main_loop主要做了三件事:
1)解析器,采用的是哈希表的解析方式。
2)开机倒数自动执行
3)命令补全,这个我们没有用到。
---------------------------------- main_loop函数解析结束------------------------------------------------------
00897: }
00899: NOTREACHED - no way out of command loop except booting */
00900: } ? end start_armboot ?
65 uboot启动2阶段总结
65.1 启动流程回顾、重点函数标出
1) 第二阶段主要是对开发板级别的硬件、软件数据结构进行初始化。
2)
init_sequence
cpu_init 空的
board_init 网卡、机器码、内存传参地址
dm9000_pre_init 网卡
gd->bd->bi_arch_number 机器码
gd->bd->bi_boot_params 内存传参地址
interrupt_init 定时器
env_init
init_baudrate gd 数据结构中波特率
serial_init 空的
console_init_f 空的
display_banner 打印启动信息
print_cpuinfo 打印CPU时钟设置信息
checkboard 检验开发板名字
dram_init gd数据结构中DDR信息
display_dram_config 打印DDR配置信息表
mem_malloc_init 初始化uboot自己维护的堆管理器的内存
mmc_initialize inand/SD卡的SoC控制器和卡的初始化
env_relocate 环境变量重定位
gd->bd->bi_ip_addr gd 数据结构赋值
gd->bd->bi_enetaddr gd数据结构赋值
devices_init 空的
jumptable_init 不用关注的
console_init_r 真正的控制台初始化
enable_interrupts 空的
loadaddr、bootfile 环境变量读出初始化全局变量
board_late_init 空的
eth_initialize 空的
x210_preboot_init LCD初始化和显示logo
check_menu_update_from_sd 检查自动更新
main_loop 主循环
65.2 启动过程特征总结
1) 第一阶段为汇编阶段、第二阶段为C阶段
2) 第一阶段在SRAM中、第二阶段在DRAM中
3) 第一阶段注重SoC内部、第二阶段注重SoC外部Board内部
65.3 移植时的注意点
1) x210_sd.h头文件中的宏定义
2) 特定硬件的初始化函数位置(譬如网卡)