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 = $2,CPU = $3,BOARD = $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}