分析uboot启动过程

时间:2022-06-27 16:48:06

1、uboot的作用。

相比于linux操作系统,uboot本身不大,能够自启动,作为嵌入式设备的引导启动,是个好的选择。此外,它具有源码开放、支持多种嵌入式操作系统、丰富的设备驱动源码等特点。

作用:

1)、为系统启动之前初始化硬件设备、为操作系统准备软件环境。

2)、引导操作系统内核启动。

 

2、uboot如何启动内核、如何传参给内核?

2.1、如何启动内核?

在嵌入式设备没有上电运行前,操作系统是放在外存中的(硬盘、外部flash、服务器等)。设备一开始上电首先执行的是uboot(BootLoader),uboot的运行有两个阶段:

 

第一个阶段: uboot对硬件的初始化,为第二阶段准备运行环境。

1.建立异常向量表。

2.设置CPU的模式。禁止中断、cpu设为SVC模式。

3.关开门狗。

4.CPU时钟初始化。

5.内存初始化。

6.复制第二阶段的代码到内存中。

7.建立映射表并开启MMU。

8.跳转到指定的内存执行第二阶段。

 

第二阶段: 初始化串口、网络硬件等,为内核设置启动参数,然后调用内核。

1.初始化IRQ/FIQ模式的栈。

2.设置系统时钟、初始化定时器,初始化串口。

3.检查环境参数是否有效,将环境参数读入指定的内存。

4.初始化网络设备。

5.调用内核、启动内核。

 

第一个阶段启动代码分析:

uboot的启动入口在uboot/cpu/xxx/start.S文件中.

1. reset:             

2. @;mrsr0,cpsr

3. @;bicr0,r0,#0x1f

4. @;orrr0,r0,#0xd3

5. @;msrcpsr,r0

6. msrcpsr_c, #0xd3@ I & F disable, Mode: 0x13 - SVC  

  2-6行禁止中断(IRQs和FIQs),cpu设置成SVC模式。

7.  _TEXT_BASE:

8. .wordTEXT_BASE

第7-8行的_TEXT_BASE就是在Makefile中指定的uboot链接地址,makefile中指定的TEXT_BASE如下:

x210_sd_config :unconfig   

@$(MKCONFIG) $(@:_config=) arm s5pc11x x210 samsung s5pc110   

@echo "TEXT_BASE = 0xc3e00000" > $(obj)board/samsung/x210/config.mk


9. _TEXT_PHY_BASE:    

10. .wordCFG_PHY_UBOOT_BASE  

11. bldisable_l2cache    

12. blset_l2cache_auxctrl_cycle  

13. blenable_l2cache  

14. bllowlevel_init/* go setup pll,mux,memory */  

          

15 ldrr0, =PRO_ID_BASE     

16 ldrr1, [r0,#OMR_OFFSET]     

15. bicr2, r1, #0xffffffc1        


第9-10行_TEXT_PHY_BASE是uboot的物理地址。

11-14的目的就是刷新并打开icache,然后调用lowlevel_init。

15-16行将r2寄存器存储一个特定值,用于指定uboot以何种方式来启动。

 

17 lowlevel_init

 ......   

......      

......      

18 ldrr0, =0xff000fff

19 bicr1, pc, r0/* r0 <- current base addr of code */

20 ldrr2, _TEXT_BASE/* r1 <- original base addr in ram */

21 bicr2, r2, r0/* r0 <- current base addr of code */

22 cmp     r1, r2         /* compare r0, r1                  */

23 beq     1f/* r0 == r1 then skip sdram init   */

24 

25 /* init system clock */

26 bl system_clock_init

27 

28 /* Memory initialize */

29 bl mem_ctrl_asm_init

30 

31 /* get ready to call C functions */

32 ldrsp, _TEXT_PHY_BASE/* setup temp stack pointer */

33 subsp, sp, #12

34 movfp, #0/* no previous frame, so fp=0 */

35 

36 ldrr0, =0xff000fff

37 bicr1, pc, r0/* r0 <- current base addr of code */

38 ldrr2, _TEXT_BASE/* r1 <- original base addr in ram */

39 bicr2, r2, r0/* r0 <- current base addr of code */

40 cmp     r1, r2           /* compare r0, r1                  */

41 beq     after_copy/* r0 == r1 then skip flash copy   */

 

第18-23行判断当前执行的代码是在SRAM还是在DDR中,如果在SRAM中,那就要进行初始化DDR,进行时钟初始化、代码的复制等,如果在DDR中,就可以直接启动。

26行进行时钟的初始化。

29行初始化内存DDR,在x210_sd.h中,起始地址的定义如下:

 

42. #define MEMORY_BASE_ADDRESS0x30000000

43. #define MEMORY_BASE_ADDRESS20x40000000

44. #define CFG_PHY_UBOOT_BASEMEMORY_BASE_ADDRESS + 0x3e00000

 

由此可知,uboot的可用物理地址为30000000-4FFFFFFF,共有512M,30000000-3FFFFFFF为DMC0,40000000-4FFFFFFF为DMC1。

32-33行设置栈,之前已经设置过一次了,那时是在SRAM中,SRAM内存空间小,现在DDR内存可以用了,重新分配大一些的栈空间,为执行第二阶段做好运行环境。

37-41行判断是否需要重定位,运行地址是在SRAM还是DDR中,如果在SRAM中,要把uboot的第二部分加载到DDR中链接地址_TEXT_BASE处。Uboot刚开始运行的时候,将第一阶段的代码从SD卡复制到SRAM中运行,第一阶段进行各种初始化,给第二阶段准备好软件的运行空间后,将第二阶段复制到链接地址执行,这过程就是重定位。通过调用movi_bl2_copy函数复制第二阶段的代码,,之后建立转换表,也就是作地址映射,代码如下第45-51行,映射完后调用enable_mmu使能MMU。

  

45. /* Set the TTB register */    

46. ldrr0, _mmu_table_base    

47. ldrr1, =CFG_PHY_UBOOT_BASE    

48. ldrr2, =0xfff00000    

49. bicr0, r0, r2    

50. orrr1, r0, r1    

51. mcrp15, 0, r1, c2, c0, 0                                       

52. ldrsp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)              


52行是再次设置栈,这次也是在DDR中,只是这次放的地方比较规范。设置栈好后,进行清理bss等,然后执行start_armboot进入第二阶段。


第二阶段:

53. _start_armboot:    

54. .wordstart_armboot    

55.     

56. init_fnc_t **init_fnc_ptr;  //typedef int (init_fnc_t) (void);     

57.      

58. #ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */                     

59. ulong gd_base;                 

60.                                            

61. gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN -      

62. CFG_STACK_SIZE - sizeof(gd_t);                                     

63. #ifdef CONFIG_USE_IRQ    

64. gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);            

65. #endif                                                             

66. gd = (gd_t*)gd_base;                                               

67. #else                                                              

68. gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));      

69. #endif                                                             

70. /* compiler optimization barrier needed for GCC >= 3.4 */          

71. __asm__ __volatile__("": : :"memory");                             

72.                                                                    

73. memset ((void*)gd, 0, sizeof (gd_t));                              

74. gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));                        

75. memset (gd->bd, 0, sizeof (bd_t));                                 

76.                                                                    

77. monitor_flash_len = _bss_start - _armboot_start;                   

78.                                                                    

79. for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) 

80. {                                                                  

81. if((*init_fnc_ptr)() !=0){                                         

82. hang ();                                                    

83. }    

84. }                                                                  

 

第66行的gd变量定义在DECLARE_GLOBAL_DATA_PTR宏中,#define DECLARE

_GLOBAL_DATA_PTR   register volatile gd_t *gd asm ("r8"),gd是一个gd_t指针类型,放在r8寄存器中。gd_t的结构体定义在include/asm-arm/global

_data.h文件中。Uboot使用它来存放很多的信息,第86-110行就是定义了gd_t结构体,它还指向了bd_t结构体,其定义在第111-127行,它有波特率参数、DDR内存大小、IP地址、机器码、环境变量等。都是一些为启动内核准备内核的参数。

73-75行为结构体分配内存空间,此分配的空间是有讲究的,此指向的起始空间通过第68行计算得到的。

79-84行是进行一些列的初始化,波特率、串口、RAM、中断等。init_sequence是一个函数指针数组,里面存放有个初始化函数的函数名。其定义在uboot/lib_arm/board.c文件中

 

86. Typedef struct global_data                                        

87. {                                                                  

88. bd_t*bd;                                                        

89.                                                        

90. unsigned longflags;       

91. unsigned longbaudrate;                                        

92. unsigned longhave_console;/* serial_init() was called*/     

93. unsigned longreloc_off;/* Relocation Offset*/            

94. unsigned longenv_addr;/*Address  of Environment struct*/        

95. unsigned longenv_valid;/* Checksum of Environment valid? */      

96. unsigned longfb_base;/* base address of frame buffer */        

97. #ifdef  CONFIG_VFD                                              

98. unsigned charvfd_type;/* display type */                        

102. #endif        

103. #if 0                                                 

104. unsigned longcpu_clk;/* CPU clock in Hz! */          

105. unsigned longbus_clk;       

106. phys_size_tram_size;/* RAM size */                   

107. unsigned longreset_status;/* reset status register at boot    

108. #endif       

109. void**jt;/* jump table */        

110. } gd_t;       

 

111.  typedef struct bd_info {   

112.     intbi_baudrate;/* serial console baudrate */  

113.     unsigned longbi_ip_addr;/* IP Address */   

114.     unsigned charbi_enetaddr[6]; /* Ethernet adress */   

115.     struct environment_s       *bi_env;   

116.     ulong      bi_arch_number;/* unique id for this board */

117.     ulong        bi_boot_params;/* where this board expects  

118.     struct/* RAM configuration */          

119.     {   

120. ulong start;          

121. ulong size;                                             

122.     }bi_dram[CONFIG_NR_DRAM_BANKS];                      

123.  #ifdefCONFIG_HAS_ETH1                                     

124.     /*second onboard ethernet port */                     

125.     unsigned char   bi_enet1addr[6];   

126. #endif   

127. } bd_t;   

128. mmc_exist = mmc_initialize(gd->bd);  

129. /* initialize environment */  

130. env_relocate ();  

131. /* IP Address  */                               

132. gd->bd->bi_ip_addr =  getenv_IPaddr ("ipaddr");     

133.    

134. /****************lxg added**************/  

135. #ifdef CONFIG_MPAD  

136. extern int x210_preboot_init(void);  

137. x210_preboot_init();  

138. #endif  

139. /****************end**********************/        

140. for (;;) {         

141. main_loop ();  

142. }                                             

143.     

第128行进行初始化uboot的堆管理器,这样uboot中也可以malloc、free这套机制来申请内存和释放内存。

第130行是将环境变量从SD卡中读到DDR中,第一次运行时SD卡中是没有环境变量的,uboot是使用自定义默认的环境变量,然后将其写入SD中,下一次启动时,就可以直接从SD卡读取环境变量了。

32行获取IP地址,第135-138行进行一些初始化,以及开始显示的LOGO。

141执行main_loop判断用户是否执行下载模式,在bootdelay秒内,用户是否有数据输入,如果没有就执行bootcmd命令,然后执行do_bootm ,->do_bootm_linux->theKernel ,执行theKernel就启动内核了。

 

144. s = getenv ("bootcmd");  

145. debug ("### main_loop: bootcmd=\"%s\"\n", s ? s :  

146.  "<UNDEFINED>");  

147.   

148. int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc,  

149.  char *argv[])  

150. do_bootm_linux (cmdtp, flag, argc, argv, &images);  

151. theKernel (0, machid, bd->bi_boot_params);         

152.     

  151行的theKernel函数参数,各参数对应着在寄存器R0、R1、R2中存放的,R0 = 0,R1 = 机器的ID,R2 = 启动参数标志列表在RAM中的启始基地址。

给内核传哪些参数? board_init函数中,有这样两行代码,

gd->bd->bi_arch_number = MACH_TYPE;  

gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);  

MACH_TYPE是板子的机器码,和内核一样。bi_boot_params就是给内核传参的存放地址。 setup_start_tag,setup_memory_tags,setup_commandline_tag,setup_initrd_tag,setup_serial_tag,setup_revision_tag,这几个函数都会标有存放的标志,告诉内核当前位置存放的是什么内容。然后调用tag_next函数指向下一个内容。

#define tag_next(t)((struct tag *)((u32 *)(t) + (t)->hdr.size))

ATAG_CORE:代码开始标志。ATAG_MEM:内存标志。ATAG_CMDLINE:命令行标志。ATAG_NONE:结束标志。

3、uboot的移植。

拿到源码包尝试进行编译,make_name_config,由Readme文档知道,根据使用的开发板,就要执行make_boadr_name_config命令进行配置,对应的配置信息在include/configs/boadr_name.h 中,提示配置成功后,在make编译一下,就会生成3个文件:

u-boot.bin:二进制可执行文件,可以直接烧入ROM、NOR Flash。 
u-boot:ELF格式的可执行文件 
u-boot.srec:Motorola S-Record格式的可执行文件。

一般是将u-boot.bin烧录SD卡,方便调试。进入sd_fusing目录下执行sd_fusing.sh脚本,里面可能需要给一些东西,如烧录sd的哪个扇区,要烧录哪个文件名等,都是在该脚本下更改。执行./sd_fusing.sh + 设备名,就可以烧录。

---编译之前的工作是确保安装正确的交叉编译链工具,这个在Makefile中更改,如下的第159行。

153. ifeq ($(ARCH),arm)   

154. #CROSS_COMPILE = arm-linux-   

155. #CROSS_COMPILE=   

156. /usr/local/arm/4.4.1-eabi-cortex-a8/usr/bin/arm-linux-   

157. #CROSS_COMPILE= /usr/local/arm/4.2.2-eabi/usr/bin/arm-linux-

158. CROSS_COMPILE=   

159. /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-   

160. Endif   

 

161. smdkv210single_config : unconfig      

162.                       

163. @$(MKCONFIG)$(@:_config=)arm s5pc11x smdkc110 samsung s5pc110

164.  @echo "TEXT_BASE = 0xc3e00000" >    

165. $(obj)board/samsung/smdkc110/config.mk    

 

make配置的过程,Makefile中的代码161-165行,配置命令“make smdkv210single_config”,实际的作用就是执行“./mkconfig smdkv210single arm s5pc11x smdkc110 samsung s5pc110”命令,相当于执行./mkconfig $1 $2 $3 $4 $5 $6(这几个变量是在mkconfig文件中使用到),几个变量被赋值为:

$1 = smdkv210single,$2 = arm,$3 = s5pc11x,$4 =  smdkc110,$5 =  samsung,$4 = s5pc110

BOARD_NAME = $1,ARCH = $2CPU = $3BOARD = $4,VENDOR = $5, SOC = $6

 

Mkconfig文件中的代码:

166. APPEND=no # Default: Create new config file    

167. BOARD_NAME="" # Name to print in make output    

168. while [ $# -gt 0 ] ; do    

169.  case "$1" in    

170.  --) shift ; break ;;    

171.  -a) shift ; APPEND=yes ;;    

172.  -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;    

173.  *)  break ;;    

174.  Esac    

175. Done    

176. [ "${BOARD_NAME}" ] || BOARD_NAME="$1"    

176行执行后,BOARD_NAME的值等于第一个参数,即“smdkv210single”。

这几个变量都在mkconfig文件中用到,就是Makefile那边传过来的,用来创建板子相关的头文件等文件。

在移植的时候要根据串口打印的内容进行修改调试,哪里出错,哪里可以正常,uboot初始化的硬件正确,传递的参数正确的情况下,就可以正常启动内核了。Uboot的配置一般在include/configs/<board_name>.h文件中,修改相关的配置信息。有关“CONFIG_”开头是设置一些参数,设置uboot的功能,选用文件中的哪一部分,而“CFG_”用来设置更为细节的参数。以下是移植三星s5pv210开发板的例子。

 

1、修改内存相关信息。

177. #define SDRAM_BANK_SIZE          0x10000000   /* 256 MB*/ 内存大小

178. #define MEMORY_BASE_ADDRESS       0x30000000    内存地    

lowlevel_init.S (board\samsung\smdkc110)文件中,将.set __base,

0x200修改为.set __base,0x300,此外还要修改smdkc110的函数修改虚拟地址映射表的基地址virt_to_phy_smdkc110(ulong addr)

return (addr - 0xc0000000 + 0x30000000)

 

2、修改inand驱动问题

uboot启动后,出现unrecognised EXT_CSD structure version 7 的提示错误。此提示版本错误。解决办法:在MMC.C文件中将ext_csd_struct > 8(大于7就行)

179.    ext_csd_struct = ext_csd[EXT_CSD_REV];   

180.   if (ext_csd_struct > 8) {   

181.    printf("unrecognised EXT_CSD structure "   

182.      "version %d\n", ext_csd_struct);   

183.   err = -1;   

184.   goto out;   

185.   }   

 

3、串口问题

串口输出的SD checksum error初始化串口控制器的代码在lowlevel_init.S中的uart_asm_init中,其中初始化串口的寄存器用ELFIN_UART_CONSOLE_BASE宏作为串口n的寄存器的基地址,结合偏移量对寄存器进行寻址初始化。到底要初始化哪个串口,取决于ELFIN_UART_CONSOLE_BASE宏。这个宏的值又由CONFIG_SERIALn(n是从1到4)来决定。CONFIG_SERIAL 宏在include\configs目录下的Smdkc110h.h文件中。ELFIN_UART_CONSOLE_BASE宏在include\S5pc110.h。

 

#if defined(CONFIG_SERIAL1)   #define ELFIN_UART_CONSOLE_BASE (ELFIN_UART_BASE + ELFIN_UART0_OFFSET)   

#elif defined(CONFIG_SERIAL2)   

#define ELFIN_UART_CONSOLE_BASE (ELFIN_UART_BASE + ELFIN_UART1_OFFSET)    

 

4、修改网络地址

头文件smdkv210single.h中:

#define CONFIG_ETHADDR   00:40:5c:26:0a:5b      

#define CONFIG_NETMASK        255.255.255.0   

#define CONFIG_IPADDR192.  168.0.20       

#define CONFIG_SERVERIP    192.168.0.10       

#define CONFIG_GATEWAYIP     192.168.0.1   

 

5、网卡移植

网卡的驱动在dm9000_pre_init函数中,网卡的驱动一般通用的,所以改其对应的地址,在smdkc110.h文件中,代码如下:

#ifdef CONFIG_DRIVER_DM9000       

#define CONFIG_DM9000_BASE (0x80000000)       

#define DM9000_IO      (CONFIG_DM9000_BASE)       

#define DM9000_DATA (CONFIG_DM9000_BASE+2)      

#endif   

CONFIG_DM9000_BASE是网卡通过SROM bank映射到SoC中地址空间中的地址,如映射到bank1上,查s5pv210手册可知,bank1的基地址是0x88000000,所以将CONFIG_DM9000

_BASE改为0x88000000,但由于网卡本身特性,应该改为0x88000300。

DM9000_IO表示访问芯片IO的基地址,直接就是CONFIG_DM9000_BASE;DM9000_DATA表示我们访问数据时的基地址,如果接到ADDR2,要+4。

 

4、uboot的命令

进入uboot控制界面后,就是接收、解析、执行命令的过程,下载文件、读内存、写内存、修改内存等。其命令都集中在uboot/common/cmd_xxx.c文件中。

bootm命令,其执行的是do_bootm函数,其他命令也一样,执行的函数是其命令在前面加有do_xxx的函数,可以传多个参数。

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])    

Uboot管理命令集的方式。每一个命令对应一个结构体,当用户输入一个命令时,会查找是否有对应的实例结构体然后执行,在内存段为_u_boot_cmd_start - _u_boot_cmd_end中找,连接脚本U-boot.ld会链接好。

命令结构体:

186. struct cmd_tbl_s {  

187. char*name;/* Command Name*/  

188. intmaxargs;/* maximum number of arguments*/  

189. intrepeatable;/* autorepeat allowed?*/  

190. /* Implementation function*/  

191. int(*cmd)(struct cmd_tbl_s *, int, int, char *[]);  

192. char*usage;/* Usage message(short)*/  

193. #ifdefCFG_LONGHELP  

194. char*help;/* Help  message(long)*/  

195. #endif  

196. #ifdef CONFIG_AUTO_COMPLETE  

197. /* do auto completion on the arguments */  

198. int(*complete)(int argc, char *argv[],charlast_char,int maxv,

199. char*cmdv[]);    

200. #endif  

201. };  

name:命令的名称,以字符串格式。

maxargs:命令接收最大的参数。

repeatable:指示这个命令是否可重复执行。重复执行是uboot命令行的一种工         作机制,就是直接按回车则执行上一条执行的命令。

cmd:函数指针,命令对应的函数的函数指针,将来执行这个命令的函数时使用   这个函数指针来调用。

usage:命令的短帮助信息。对命令的简单描述。

help:命令的长帮助信息。细节的帮助信息。

complete:函数指针,指向这个命令的自动补全的函数。

命令的定义。每个命令都通过U_BOOT_CMD宏来定义,这个宏在uboot/common

/command.h文件中,如下所示:

202. #define Struct_Section  __attribute__ ((unused,section    

203. (".u_boot_cmd")))    

204. #ifdef  CFG_LONGHELP    

205. #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \    

206. cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name,  maxargs,

207.  rep,    

208. cmd, usage, help}    

209. #else/* no long help info */    

210. #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \    

211. cmd_tbl_t  __u_boot_cmd_##name Struct_Section = {#name,maxargs,

212. rep,      

213. cmd, usage}        

214. #endif                      

 

version命令:

U_BOOT_CMD(        

version,1,1,do_version,        

"version - print monitor version\n",        

NULL        

);        

 

这个宏替换后变成:

cmd_tbl_t __u_boot_cmd_version __attribute__ ((unused,section          

(".u_boot_cmd"))) = {version, 1,1, do_version, "version - print monitor  

version\n", NULL}