Linux系统移植之—uboot移植,你们要的uboot终于来了,堪称精品
作为一名过来人,uboot、kernel对每个学linux的来说都有很深的情谊,因为它们是一个系统跑起来的最基础,每个学linux的都会首先接触到。而它们本身就是一个精美的小系统,里边代码所体现的逻辑、算法以及每个绝妙的C知识点都让你沉醉其中。
uboot 属于bootloader的一种,是用来引导启动内核的,它的最终目的就是,从flash中读出内核,放到内存中,启动内核。具体内容如下:
1 uboot 的介绍及系统结构
1.1 uboot 介绍
1.2 获取 uboot
1.3 uboot 体系结构
1.3.1 uboot 目录结构
2 uboot 的启动过程及工作原理
2.1 启动模式介绍
2.2 阶段 1 介绍
2.2.1 定义入口
2.2.2 设置异常向量
2.2.3 设置 CPU 的模式为 SVC 模式
2.2.4 关闭看门狗
2.2.5 禁掉所有中断
2.2.6 设置以 CPU 的频率
2.2.7 设置 CP15
2.2.8 配置内存区控制寄存器
2.2.9 安装 UBOOT 使的栈空间
2.2.10 BSS 段清 0
2.2.11 搬移 Nand Flash 代码
2.2.12 进入 C 代码部分
2.3 阶段 2 的 C 语言代码部分
2.3.1 调用一系列的初始化函数
2.3.2 初始化网络设备
2.3.3 进入主 UBOOT 命令行
2.4 代码搬运
3 uboot 的移 植过程
3.1 环境
3.2 步骤
3.2.1 修改 Makefile
3.2.2 在 board 子目录中建立 crane2410
3.2.3 在 include/configs/中建立配置头文件
3.2.4 指定交叉编译工具的路径
3.2.5 测试编译能否成功
3.2.6 修改 lowlevel_init.S 文件
2.9 UBOOT 的 Nand Flash 移植
3.2.8 重新编译 uboot
3.2.9 把 uboot 烧入 flash
4.2 常用命令使用说明
4.2.1 askenv(F)
在标准输入(stdin)获得环境变量。
4.2.2 autoscr
从内存(Memory)运行脚本。(注意,从下载地址开始,例如我们的开发板是从 0x30008000 处开始运
行).
CRANE2410 # autoscr 0x30008000
## Executing script at 30008000
4.2.3 base
打印或者设置当前指令与下载地址的地址偏移。
4.2.4 bdinfo
打印开发板信息
CRANE2410 # bdinfo
-arch_number = 0x000000C1 (CPU 体系结构号)
-env_t = 0x00000000 (环境变量)
-boot_params = 0x30000100 (启动引导参数)
-DRAM bank = 0x00000000 (内存区)
--> start = 0x30000000 (SDRAM 起始地址)
--> size = 0x04000000 (SDRAM 大小)
-ethaddr = 01:23:45:67:89:AB (以太网地址)
-ip_addr = 192.168.1.5 (IP 地址)
-baudrate = 115200 bps (波特率)
4.2.5 bootp
通过网络使用 Bootp 或者 TFTP 协议引导境像文件。
CRANE2410 # help bootp
bootp [loadAddress] [bootfilename]
4.2.6 bootelf
默认从 0x30008000 引导 elf 格式的文件(vmlinux)
CRANE2410 # help bootelf
bootelf [address] - load address of ELF image.
4.2.7 bootd(=boot)
引导的默认命令,即运行 U-BOOT 中在“include/configs/smdk2410.h” 中设置的“bootcmd” 中
的命令。如下:
#define CONFIG_BOOTCOMMAND "tftp 0x30008000 uImage; bootm 0x30008000";
在命令下做如下试验:
CRANE2410 # set bootcmd printenv
CRANE2410 # boot
bootdelay=3
baudrate=115200
ethaddr=01:23:45:67:89:abCRANE2410 # bootd
bootdelay=3
baudrate=115200
ethaddr=01:23:45:67:89:ab
4.2.8 tftp(tftpboot)
即将内核镜像文件从 PC 中下载到 SDRAM 的指定地址,然后通过 bootm 来引导内核,前提是所用 PC 要安装设
置 tftp 服务。
下载信息:
CRANE2410 # tftp 0x30008000 zImage
TFTP from server 10.0.0.1; our IP address is 10.0.0.110
Filename 'zImage'.
Load address: 0x30008000
Loading: #################################################################
#################################################################
#################################################
done
Bytes transferred = 913880 (df1d8 hex)
4.2.9 bootm
内核的入口地址开始引导内核。
CRANE2410 # bootm 0x30008000
## Booting image at 30008000 ...
Starting kernel ...
Uncompressing
Linux......................................................................
done, .
4.2.10 go
直接跳转到可执行文件的入口地址,执行可执行文件。
CRANE2410 # go 0x30008000
## Starting application at 0x30008000 ...
4.2.11 cmp
对输入的两段内存地址进行比较。
CRANE2410 # cmp 0x30008000 0x30008040 64
word at 0x30008000 (0xe321f0d3) != word at 0x30008040 (0xc022020c)
Total of 0 words were the same
CRANE2410 # cmp 0x30008000 0x30008000 64
Total of 100 words were the same
4.2.12 coninfo
打印所有控制设备和信息,例如
-List of available devices:
-serial 80000003 SIO stdin stdout stderr
4.2.13 cp
内存拷贝,cp 源地址 目的地址 拷贝大小(字节)
CRANE2410 # help cp
cp [.b, .w, .l] source target count
ANE2410 # cp 0x30008000 0x3000f000 644.2.14 date
获得/设置/重设日期和时间
CRANE2410 # date
Date: 2006-6-6 (Tuesday) Time: 06:06:06
4.2.15 erase(F)
擦除 FLASH MEMORY, 由于该 ARM 板没有 Nor Flash, 所有不支持该命令.
CRANE2410 # help erase
erase start end
- erase FLASH from addr 'start' to addr 'end'
erase start +len
- erase FLASH from addr 'start' to the end of sect w/addr 'start'+'len'-1
erase N:SF[-SL]
- erase sectors SF-SL in FLASH bank # N
erase bank N
- erase FLASH bank # N
erase all
- erase all FLASH banks
4.2.16 flinfo(F)
打印 Nor Flash 信息, 由于该 ARM 板没有 Nor Flash, 所有不支持该命令.
4.2.17 iminfo
打印和校验内核镜像头, 内核的起始地址由 CFG_LOAD_ADDR 指定:
#define CFG_LOAD_ADDR 0x30008000 /* default load address */
该宏在 include/configs/crane2410.h 中定义.
CRANE2410 # iminfo
## Checking Image at 30008000 ...
Image Name: Linux-2.6.14.1
Created: 2006-06-28 7:43:01 UTC
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1047080 Bytes = 1022.5 kB
Load Address: 30008000
Entry Point: 30008040
Verifying Checksum ... OK
4.2.18 loadb
从串口下载二进制文件
CRANE2410 # loadb
## Ready for binary (kermit) download to 0x30008000 at 115200 bps...
## Total Size = 0x00000000 = 0 Bytes
## Start Addr = 0x30008000
4.2.19 md
显示指定内存地址中的内容
CRANE2410 # md 0
00000000: ea000012 e59ff014 e59ff014 e59ff014 ................
00000010: e59ff014 e59ff014 e59ff014 e59ff014 ................
00000020: 33f80220 33f80280 33f802e0 33f80340 ..3...3...3@..3
00000030: 33f803a0 33f80400 33f80460 deadbeef ...3...3`..3....
00000040: 33f80000 33f80000 33f9c0b4 33fa019c ...3...3...3...3
00000050: e10f0000 e3c0001f e38000d3 e129f000 ..............).00000060: e3a00453 e3a01000 e5801000 e3e01000 S...............
00000070: e59f0444 e5801000 e59f1440 e59f0440 D.......@...@...
00000080: e5801000 e59f043c e3a01003 e5801000 ....<...........
00000090: eb000051 e24f009c e51f1060 e1500001 Q.....O.`.....P.
000000a0: 0a000007 e51f2068 e51f3068 e0432002 ....h ..h0... C.
000000b0: e0802002 e8b007f8 e8a107f8 e1500002 . ............P.
000000c0: dafffffb e51f008c e2400803 e2400080 ..........@...@.
000000d0: e240d00c e51f0094 e51f1094 e3a02000 ..@.......... ..
000000e0: e5802000 e2800004 e1500001 dafffffb . ........P.....
000000f0: eb000006 e59f13d0 e281f000 e1a00000 ................
4.2.20 mm
顺序显示指定地址往后的内存中的内容,可同时修改,地址自动递增。
CRANE2410 # mm 0x30008000
30008000: e1a00000 ? fffff
30008004: e1a00000 ? eeeeee
30008008: e1a00000 ? q
CRANE2410 # md 30008000
30008000: 000fffff 00eeeeee e1a00000 e1a00000 ................
30008010: e1a00000 e1a00000 e1a00000 e1a00000 ................
30008020: ea000002 016f2818 00000000 000df1d8 .....(o.........
30008030: e1a07001 e3a08000 e10f2000 e3120003 .p....... ......
4.2.21 mtest
简单的 RAM 检测
CRANE2410 # mtest
Pattern FFFFFFFD Writing... Reading...
4.2.22 mw
向内存地址写内容
CRANE2410 # md 30008000
30008000: ffffdffd ffffdffc ffffdffb ffffdffa ................
CRANE2410 # mw 30008000 0 4
CRANE2410 # md 30008000
30008000: 00000000 00000000 00000000 00000000 ................
4.2.23 nm
修改内存地址, 地址不递增
CRANE2410 # nm 30008000
30008000: de4c457f ? 00000000
30008000: 00000000 ? 11111111
30008000: 11111111 ?
4.2.24 printenv
打印环境变量
CRANE2410 # printenv
bootdelay=3
baudrate=115200
ethaddr=01:23:45:67:89:ab
ipaddr=10.0.0.110
serverip=10.0.0.1
netmask=255.255.255.0
stdin=serial
stdout=serialstderr=serial
Environment size: 153/65532 bytes
4.2.25 ping
ping 主机
CRANE2410 # ping 10.0.0.1
host 10.0.0.1 is alive
4.2.26 reset
复位 CPU
4.2.27 run
运行已经定义好的 U-BOOT 的命令
CRANE2410 # set myenv ping 10.0.0.1
CRANE2410 # run myenv
host 10.0.0.1 is alive
4.2.28 saveenv(F)
保存设定的环境变量
4.2.29 setenv
设置环境变量
CRANE2410 # setenv ipaddr 10.0.0.254
CRANE2410 # printenv
ipaddr=10.0.0.254
4.2.30 sleep
命令延时执行时间
CRANE2410 # sleep 1
4.2.31 version
打印 U-BOOT 版本信息
CRANE2410 # version
U-Boot 1.1.4 (Jul 4 2006 - 12:42:27)
4.2.32 nand info
打印 nand flash 信息
CRANE2410 # nand info
Device 0: Samsung K9F1208U0B at 0x4e000000 (64 MB, 16 kB sector)
4.2.33 nand device <n>
显示某个 nand 设备
CRANE2410 # nand device 0
Device 0: Samsung K9F1208U0B at 0x4e000000 (64 MB, 16 kB sector)
... is now current device
4.2.34 nand bad
CRANE2410 # nand bad
Device 0 bad blocks:4.2.35 nand read
nand read InAddr FlAddr size
InAddr: 从 nand flash 中读到内存的起始地址。
FlAddr: nand flash 的起始地址。
size: 从 nand flash 中读取的数据的大小。
CRANE2410 # nand read 0x30008000 0 0x100000
NAND read: device 0 offset 0, size 1048576 ...
1048576 bytes read: OK
4.2.36 nand erease
nand erase FlAddr size
FlAddr: nand flash 的起始地址
size: 从 nand flash 中擦除数据块的大小
CRANE2410 # nand erase 0x100000 0x20000
NAND erase: device 0 offset 1048576, size 131072 ... OK
4.2.37 nand write
nand write InAddr FlAddr size
InAddr: 写到 Nand Flash 中的数据在内存的起始地址
FlAddr: Nand Flash 的起始地址
size: 数据的大小
CRANE2410 # nand write 0x30f00000 0x100000 0x20000
NAND write: device 0 offset 1048576, size 131072 ...
131072 bytes written: OK
4.2.37 nboot
u-boot-1.1.4 代码对于 nboot 命令的帮助不正确,修改如下:
正确的顺序为:
nboot InAddr dev FlAddr
InAddr: 需要装载到的内存的地址。
FlAddr: 在 nand flash 上 uImage 存放的地址
dev: 设备号
需要提前设置环境变量,否则 nboot 不会调用 bootm
CRANE2410 #setenv autostart yes
CRANE2410 # nboot 30008000 0 100000
Loading from device 0: <NULL> at 0x4e000000 (offset 0x100000)
Image Name: Linux-2.6.14.3
Created: 2006-07-06 7:31:52 UTC
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 897428 Bytes = 876.4 kB
Load Address: 30008000
Entry Point: 30008040
Automatic boot of image at addr 0x30008000 ...
## Booting image at 30008000 ...
Starting kernel ...
4.3 命令简写说明
所以命令都可以简写,只要命令前面的一部分不会跟其它命令相同,就可以不用写全整个命令.save 命令
CRANE2410 # sa
Saving Environment to Flash...
Un-Protected 1 sectors
Erasing Flash...Erasing sector 10 ... Erased 1 sectors
4.4 把文件写入 NandFlash
如果把一个传到内存中的文件写入到 Nand Flash 中, 如:新的 uboot.bin, zImage(内核),
rootfs 等, 如果做呢?我们可以用 Nand Flash 命令来完成. 但是 Nand Flash 写时,必须先要把 Nand
Flash 的写入区全部擦除后,才能写. 下面以把内存 0x30008000 起长度为 0x20000 的内容写到 Nand
Flash 中的 0x100000 为例.
CRANE2410 # nand erase 0x100000 20000
NAND erase: device 0 offset 1048576, size 131072 ... OK
CRANE2410 # nand write 0x30008000 0x100000 0x20000
NAND write: device 0 offset 1048576, size 131072 ...
131072 bytes written: OK
「正点原子Linux连载」第三十二章U-Boot启动流程详解(二)
1)实验平台:正点原子Linux开发板
2)摘自《正点原子 I.MX6U嵌入式Linux驱动开发指南》
关注官方微信号公众号,获取更多资料:正点原子
32.2.4_main函数详解
_main函数定义在文件arch/arm/lib/crt0.S中,函数内容如下:
示例代码32.2.4.1 crt0.S代码段
63/*
64 * entry point of crt0 sequence
65 */
66
67 ENTRY( _main)
68
69/*
70 * Set up initial C runtime environment and call board_init_f(0).
71 */
72
73 #if defined( CONFIG_SPL_BUILD)&& defined( CONFIG_SPL_STACK)
74 ldr sp,=( CONFIG_SPL_STACK)
75 #else
76 ldr sp,=( CONFIG_SYS_INIT_SP_ADDR)
77 #endif
78 #if defined( CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
79 mov r3, sp
80 bic r3, r3, #7
81 mov sp, r3
82 #else
83 bic sp, sp, #7/* 8-byte alignment for ABI compliance */
84 #endif
85 mov r0, sp
86 bl board_init_f_alloc_reserve
87 mov sp, r0
88/* set up gd here, outside any C code */
89 mov r9, r0
90 bl board_init_f_init_reserve
91
92 mov r0, #0
93 bl board_init_f
94
95 #if! defined( CONFIG_SPL_BUILD)
96
97/*
98 * Set up intermediate environment (new sp and gd) and call
99 * relocate_code(addr_moni). Trick here is that we'll return
100 * 'here' but relocated.
101 */
102
103 ldr sp,[ r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
104 #if defined( CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
105 mov r3, sp
106 bic r3, r3, #7
107 mov sp, r3
108 #else
109 bic sp, sp, #7/* 8-byte alignment for ABI compliance */
110 #endif
111 ldr r9,[ r9, #GD_BD] /* r9 = gd->bd */
112 sub r9, r9, #GD_SIZE /* new GD is below bd */
113
114 adr lr, here
115 ldr r0,[ r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
116 add lr, lr, r0
117 #if defined( CONFIG_CPU_V7M)
118 orr lr, #1/* As required by Thumb-only */
119 #endif
120 ldr r0,[ r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
121 b relocate_code
122 here:
123/*
124 * now relocate vectors
125 */
126
127 bl relocate_vectors
128
129/* Set up final (full) environment */
130
131 bl c_runtime_cpu_setup /* we still call old routine here */
132 #endif
133 #if! defined( CONFIG_SPL_BUILD)|| defined( CONFIG_SPL_FRAMEWORK)
134 # ifdef CONFIG_SPL_BUILD
135/* Use a DRAM stack for the rest of SPL, if requested */
136 bl spl_relocate_stack_gd
137 cmp r0, #0
138 movne sp, r0
139 movne r9, r0
140 # endif
141 ldr r0,= __bss_start /* this is auto-relocated! */
142
143 #ifdef CONFIG_USE_ARCH_MEMSET
144 ldr r3,= __bss_end /* this is auto-relocated! */
145 mov r1, #0x00000000/* prepare zero to clear BSS */
146
147 subs r2, r3, r0 /* r2 = memset len */
148 bl memset
149 #else
150 ldr r1,= __bss_end /* this is auto-relocated! */
151 mov r2, #0x00000000/* prepare zero to clear BSS */
152
153 clbss_l: cmp r0, r1 /* while not at end of BSS */
154 #if defined( CONFIG_CPU_V7M)
155 itt lo
156 #endif
157 strlo r2,[ r0] /* clear 32-bit BSS word */
158 addlo r0, r0, #4/* move to next */
159 blo clbss_l
160 #endif
161
162 #if! defined( CONFIG_SPL_BUILD)
163 bl coloured_LED_init
164 bl red_led_on
165 #endif
166/* call board_init_r(gd_t *id, ulong dest_addr) */
167 mov r0, r9 /* gd_t */
168 ldr r1,[ r9, #GD_RELOCADDR] /* dest_addr */
169/* call board_init_r */
170 #if defined( CONFIG_SYS_THUMB_BUILD)
171 ldr lr,= board_init_r /* this is auto-relocated! */
172 bx lr
173 #else
174 ldr pc,= board_init_r /* this is auto-relocated! */
175 #endif
176/* we should not return here. */
177 #endif
178
179 ENDPROC( _main)
第76行,设置sp指针为CONFIG_SYS_INIT_SP_ADDR,也就是sp指向0X0091FF00。
第83行,sp做8字节对齐。
第85行,读取sp到寄存器r0里面,此时r0=0X0091FF00。
第86行,调用函数board_init_f_alloc_reserve,此函数有一个参数,参数为r0中的值,也就是0X0091FF00,此函数定义在文件common/init/board_init.c中,内容如下:
示例代码32.2.4.2 board_init.c代码段
56 ulong board_init_f_alloc_reserve( ulong top)
57{
58 /* Reserve early malloc arena */
59 #if defined( CONFIG_SYS_MALLOC_F)
60 top -= CONFIG_SYS_MALLOC_F_LEN;
61 #endif
62 /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
63 top = rounddown( top-sizeof( struct global_data), 16);
64
65 return top;
66}
函数board_init_f_alloc_reserve主要是留出早期的malloc内存区域和gd内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X400(在文件include/generated/autoconf.h中定义),sizeof(struct global_data)=248(GD_SIZE值),完成以后的内存分布如图32.2.4.1所示:
图32.2.4.1 内存分布图
函数board_init_f_alloc_reserve是有返回值的,返回值为新的top值,从图32.2.4.1可知,此时top=0X0091FA00。
继续回到示例代码32.2.4.1中,第87行,将r0写入到sp里面,r0保存着函数board_init_f_alloc_reserve的返回值,所以这一句也就是设置sp=0X0091FA00。
第89行,将r0寄存器的值写到寄存器r9里面,因为r9寄存器存放着全局变量gd的地址,在文件arch/arm/include/asm/global_data.h中有如图32.2.4.2所示宏定义:
图32.2.4.2 DECLARE_GLOBAL_DATA_PTR宏定义
从图32.2.4.2可以看出,uboot中定义了一个指向gd_t的指针gd,gd存放在寄存器r9里面的,因此gd是个全局变量。gd_t是个结构体,在include/asm-generic/global_data.h里面有定义,gd_定义如下:
示例代码32.2.4.3 global_data.h代码段
27typedef struct global_data {
28 bd_t * bd;
29unsignedlong flags;
30unsignedint baudrate;
31unsignedlong cpu_clk; /* CPU clock in Hz! */
32unsignedlong bus_clk;
33/* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
34unsignedlong pci_clk;
35unsignedlong mem_clk;
36 #if defined( CONFIG_LCD)|| defined( CONFIG_VIDEO)
37unsignedlong fb_base; /* Base address of framebuffer mem */
38 #endif
......
121 #ifdef CONFIG_DM_VIDEO
122 ulong video_top; /* Top of video frame buffer area */
123 ulong video_bottom; /* Bottom of video frame buffer area */
124 #endif
125} gd_t;
因此这一行代码就是设置gd所指向的位置,也就是gd指向0X0091FA00。
继续回到示例代码32.2.4.1中,第90行调用函数board_init_f_init_reserve,此函数在文件common/init/board_init.c中有定义,函数内容如下:
示例代码32.2.4.4 board_init.c代码段
110void board_init_f_init_reserve( ulong base)
111{
112struct global_data * gd_ptr;
113 #ifndef _USE_MEMCPY
114int* ptr;
115 #endif
116
117/*
118 * clear GD entirely and set it up.
119 * Use gd_ptr, as gd may not be properly set yet.
120 */
121
122 gd_ptr =( struct global_data *) base;
123/* zero the area */
124 #ifdef _USE_MEMCPY
125 memset( gd_ptr, '\0',sizeof(* gd));
126 #else
127for( ptr =( int*) gd_ptr; ptr <( int*)( gd_ptr + 1);)
128* ptr++= 0;
129 #endif
130/* set GD unless architecture did it already */
131 #if! defined( CONFIG_ARM)
132 arch_setup_gd( gd_ptr);
133 #endif
134/* next alloc will be higher by one GD plus 16-byte alignment */
135 base += roundup(sizeof( struct global_data), 16);
136
137/*
138 * record early malloc arena start.
139 * Use gd as it is now properly set for all architectures.
140 */
141
142 #if defined( CONFIG_SYS_MALLOC_F)
143/* go down one 'early malloc arena' */
144 gd-> malloc_base = base;
145/* next alloc will be higher by one 'early malloc arena' size */
146 base += CONFIG_SYS_MALLOC_F_LEN;
147 #endif
148}
可以看出,此函数用于初始化gd,其实就是清零处理。另外,此函数还设置了gd->malloc_base为gd基地址+gd大小=0X0091FA00+248=0X0091FAF8,在做16字节对齐,最终gd->malloc_base=0X0091FB00,这个也就是earlymalloc的起始地址。
继续回到示例代码32.2.4.1中,第92行设置R0为0。
第93行,调用board_init_f函数,此函数定义在文件common/board_f.c中!主要用来初始化DDR,定时器,完成代码拷贝等等,此函数我们后面在详细的分析。
第103行,重新设置环境(sp和gd)、获取gd->start_addr_sp的值赋给sp,在函数board_init_f中会初始化gd的所有成员变量,其中gd->start_addr_sp=0X9EF44E90,所以这里相当于设置sp=gd->start_addr_sp=0X9EF44E90。0X9EF44E90是DDR中的地址,说明新的sp和gd将会存放到DDR中,而不是内部的RAM了。GD_START_ADDR_SP=64,参考示例代码32.2.2.4。
第109行,sp做8字节对齐。
第111行,获取gd->bd的地址赋给r9,此时r9存放的是老的gd,这里通过获取gd->bd的地址来计算出新的gd的位置。GD_BD=0,参考示例代码32.2.2.4。
第112行,新的gd在bd下面,所以r9减去gd的大小就是新的gd的位置,获取到新的gd的位置以后赋值给r9。
第114行,设置lr寄存器为here,这样后面执行其他函数返回的时候就返回到了第122行的here位置处。
第115,读取gd->reloc_off的值复制给r0寄存器,GD_RELOC_OFF=68,参考示例代码32.2.2.4。
第116行,lr寄存器的值加上r0寄存器的值,重新赋值给lr寄存器。因为接下来要重定位代码,也就是把代码拷贝到新的地方去(现在的uboot存放的起始地址为0X87800000,下面要将uboot拷贝到DDR最后面的地址空间出,将0X87800000开始的内存空出来),其中就包括here,因此lr中的here要使用重定位后的位置。
第120行,读取gd->relocaddr的值赋给r0寄存器,此时r0寄存器就保存着uboot要拷贝的目的地址,为0X9FF47000。GD_RELOCADDR=48,参考示例代码32.2.2.4。
第121行,调用函数relocate_code,也就是代码重定位函数,此函数负责将uboot拷贝到新的地方去,此函数定义在文件arch/arm/lib/relocate.S中稍后会详细分析此函数。
第127行,调用函数relocate_vectors,对中断向量表做重定位,此函数定义在文件arch/arm/lib/relocate.S中,稍后会详细分析此函数。
第131行,调用函数c_runtime_cpu_setup,此函数定义在文件arch/arm/cpu/armv7/start.S中,函数内容如下:
示例代码32.2.4.5 start.S代码段
77 ENTRY( c_runtime_cpu_setup)
78/*
79 * If I-cache is enabled invalidate it
80 */
81 #ifndef CONFIG_SYS_ICACHE_OFF
82 mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
83 mcr p15, 0, r0, c7, c10, 4 @ DSB
84 mcr p15, 0, r0, c7, c5, 4 @ ISB
85 #endif
86
87 bx lr
88
89 ENDPROC( c_runtime_cpu_setup)
第141~159行,清除BSS段。
第167行,设置函数board_init_r的两个参数,函数board_init_r声明如下:
board_init_r(gd_t *id, ulong dest_addr)
第一个参数是gd,因此读取r9保存到r0里面。
第168行,设置函数board_init_r的第二个参数是目的地址,因此r1=gd->relocaddr。
第174行、调用函数board_init_r,此函数定义在文件common/board_r.c中,稍后会详细的分析此函数。
这个就是_main函数的运行流程,在_main函数里面调用了board_init_f、relocate_code、relocate_vectors和board_init_r这4个函数,接下来依次看一下这4个函数都是干啥的。
32.2.5 board_init_f函数详解
_main中会board_init_f函数,board_init_f函数主要有两个工作:
①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
②、初始化gd的各个成员变量,uboot会将自己重定位到DRAM最后面的地址区域,也就是将自己拷贝到DRAM最后面的内存区域中。这么做的目的是给Linux腾出空间,防止Linuxkernel覆盖掉uboot,将DRAM前面的区域完整的空出来。在拷贝之前肯定要给uboot各部分分配好内存位置和大小,比如gd应该存放到哪个位置,malloc内存池应该存放到哪个位置等等。这些信息都保存在gd的成员变量中,因此要对gd的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位uboot的时候就会用到这个内存“分配图”。
此函数定义在文件common/board_f.c中定义,代码如下:
示例代码32.2.5.1 board_f.c代码段
1035void board_init_f( ulong boot_flags)
1036{
1037 #ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA
1038/*
1039 * For some archtectures, global data is initialized and used
1040 * before calling this function. The data should be preserved.
1041 * For others, CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined
1042 * and use the stack here to host global data until relocation.
1043 */
1044 gd_t data;
1045
1046 gd =& data;
1047
1048/*
1049 * Clear global data before it is accessed at debug print
1050 * in initcall_run_list. Otherwise the debug print probably
1051 * get the wrong vaule of gd->have_console.
1052 */
1053 zero_global_data();
1054 #endif
1055
1056 gd-> flags = boot_flags;
1057 gd-> have_console = 0;
1058
1059if( initcall_run_list( init_sequence_f))
1060 hang();
1061
1062 #if! defined( CONFIG_ARM)&&! defined( CONFIG_SANDBOX)&& \
1063! defined( CONFIG_EFI_APP)
1064/* NOTREACHED - jump_to_copy() does not return */
1065 hang();
1066 #endif
1067}
因为没有定义CONFIG_SYS_GENERIC_GLOBAL_DATA,所以第1037~1054行代码无效。
第1056行,初始化gd->flags=boot_flags=0。
第1057行,设置gd->have_console=0。
重点在第1059行!通过函数initcall_run_list来运行初始化序列init_sequence_f里面的一些列函数,init_sequence_f里面包含了一系列的初始化函数,init_sequence_f也是定义在文件common/board_f.c中,由于init_sequence_f的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的init_sequence_f定义如下:
示例代码32.2.5.1 board_f.c代码段
/*****************去掉条件编译语句后的init_sequence_f***************/
1static init_fnc_t init_sequence_f[]={
2 setup_mon_len,
3 initf_malloc,
4 initf_console_record,
5 arch_cpu_init, /* basic arch cpu dependent setup */
6 initf_dm,
7 arch_cpu_init_dm,
8 mark_bootstage, /* need timer, go after init dm */
9 board_early_init_f,
10 timer_init, /* initialize timer */
11 board_postclk_init,
12 get_clocks,
13 env_init, /* initialize environment */
14 init_baud_rate, /* initialze baudrate settings */
15 serial_init, /* serial communications setup */
16 console_init_f, /* stage 1 init of console */
17 display_options, /* say that we are here */
18 display_text_info, /* show debugging info if required */
19 print_cpuinfo, /* display cpu info (and speed) */
20 show_board_info,
21 INIT_FUNC_WATCHDOG_INIT
22 INIT_FUNC_WATCHDOG_RESET
23 init_func_i2c,
24 announce_dram_init,
25 /* TODO: unify all these dram functions? */
26 dram_init, /* configure available RAM banks */
27 post_init_f,
28 INIT_FUNC_WATCHDOG_RESET
29 testdram,
30 INIT_FUNC_WATCHDOG_RESET
31 INIT_FUNC_WATCHDOG_RESET
32 /*
33 * Now that we have DRAM mapped and working, we can
34 * relocate the code and continue running from DRAM.
35 *
36 * Reserve memory at end of RAM for (top down in that order):
37 * - area that won't get touched by U-Boot and Linux (optional)
38 * - kernel log buffer
39 * - protected RAM
40 * - LCD framebuffer
41 * - monitor code
42 * - board info struct
43 */
44 setup_dest_addr,
45 reserve_round_4k,
46 reserve_mmu,
47 reserve_trace,
48 reserve_uboot,
49 reserve_malloc,
50 reserve_board,
51 setup_machine,
52 reserve_global_data,
53 reserve_fdt,
54 reserve_arch,
55 reserve_stacks,
56 setup_dram_config,
57 show_dram_config,
58 display_new_sp,
59 INIT_FUNC_WATCHDOG_RESET
60 reloc_fdt,
61 setup_reloc,
62 NULL,
63};
接下来分析以上函数执行完以后的结果:
第2行,setup_mon_len函数设置gd的mon_len成员变量,此处为__bss_end -_start,也就是整个代码的长度。0X878A8E74-0x87800000=0XA8E74,这个就是代码长度
第3行,initf_malloc函数初始化gd中跟malloc有关的成员变量,比如malloc_limit,此函数会设置gd->malloc_limit = CONFIG_SYS_MALLOC_F_LEN=0X400。malloc_limit表示malloc内存池大小。
第4行,initf_console_record,如果定义了宏CONFIG_CONSOLE_RECORD和宏CONFIG_SYS_MALLOC_F_LEN的话此函数就会调用函数console_record_init,但是IMX6ULL的uboot没有定义宏CONFIG_CONSOLE_RECORD,所以此函数直接返回0。
第5行,arch_cpu_init函数,初始化架构相关的内容,CPU级别的操作。
第6行,initf_dm函数,驱动模型的一些初始化。
第7行,arch_cpu_init_dm函数未实现。
第8行,mark_bootstage函数应该是和啥标记有关的
第9行,board_early_init_f函数,板子相关的早期的一些初始化设置,I.MX6ULL用来初始化串口的IO配置
第10行,timer_init,初始化定时器,Cortex-A7内核有一个定时器,这里初始化的就是Cortex-A内核的那个定时器。通过这个定时器来为uboot提供时间。就跟Cortex-M内核Systick定时器一样。关于Cortex-A内部定时器的详细内容,请参考文档《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》的“Chapter B8 The Generic Timer”章节。
第11行,board_postclk_init,对于I.MX6ULL来说是设置VDDSOC电压。
第12行,get_clocks函数用于获取一些时钟值,I.MX6ULL获取的是sdhc_clk时钟,也就是SD卡外设的时钟。
第13行,env_init函数是和环境变量有关的,设置gd的成员变量env_addr,也就是环境变量的保存地址。
第14行,init_baud_rate函数用于初始化波特率,根据环境变量baudrate来初始化gd->baudrate。
第15行,serial_init,初始化串口。
第16行,console_init_f,设置gd->have_console 为1,表示有个控制台,此函数也将前面暂存在缓冲区中的数据通过控制台打印出来。
第17行、display_options,通过串口输出一些信息,如图32.2.5.1所示:
图32.2.5.1 串口信息输出
第18行,display_text_info,打印一些文本信息,如果开启UBOOT的DEBUG功能的话就会输出text_base、bss_start、bss_end,形式如下:
debug("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",text_base, bss_start, bss_end);
结果如图32.2.5.2所示:
图32.2.5.2 文本信息
第19行,print_cpuinfo函数用于打印CPU信息,结果如图32.2.5.3所示:
图32.2.5.3 CPU信息
图32.2.5.3 CPU信息
第20行,show_board_info函数用于打印板子信息,会调用checkboard函数,结果如图32.2.5.4所示:
图32.2.5.4 板子信息
第21行,INIT_FUNC_WATCHDOG_INIT,初始化看门狗,对于I.MX6ULL来说是空函数
第22行,INIT_FUNC_WATCHDOG_RESET,复位看门狗,对于I.MX6ULL来说是空函数
第23行,init_func_i2c函数用于初始化I2C,初始化完成以后会输出如图32.2.5.5所示信息:
图32.2.5.5 I2C初始化信息输出
第24行,announce_dram_init,此函数很简单,就是输出字符串“DRAM:”
第26行,dram_init,并非真正的初始化DDR,只是设置gd->ram_size的值,对于正点原子I.MX6ULL开发板EMMC版本核心板来说就是512MB。
第27行,post_init_f,此函数用来完成一些测试,初始化gd->post_init_f_time
第29行,testdram,测试DRAM,空函数。
第44行,setup_dest_addr函数,设置目的地址,设置gd->ram_size,gd->ram_top,gd->relocaddr这三个的值。接下来我们会遇到很多跟数值有关的设置,如果直接看代码分析的话就太费时间了,我可以修改uboot代码,直接将这些值通过串口打印出来,比如这里我们修改文件common/board_f.c,因为setup_dest_addr函数定义在文件common/board_f.c中,在setup_dest_addr函数输入如图32.2.5.6所示内容:
图32.2.5.6 添加print函数打印成员变量值
设置好以后重新编译uboot,然后烧写到SD卡中,选择SD卡启动,重启开发板,打开SecureCRT,uboot会输出如图32.2.5.7所示信息:
从图32.2.5.7可以看出:
gd->ram_size = 0X20000000 //ram大小为0X20000000=512MB
gd->ram_top = 0XA0000000 //ram最高地址为0X80000000+0X20000000=0XA0000000
gd->relocaddr = 0XA0000000 //重定位后最高地址为0XA0000000
第45行,reserve_round_4k函数用于对 gd->relocaddr做4KB对齐,因为gd->relocaddr=0XA0000000,已经是4K对齐了,所以调整后不变。
第46行,reserve_mmu,留出MMU的TLB表的位置,分配MMU的TLB表内存以后会对gd->relocaddr做64K字节对齐。完成以后gd->arch.tlb_size、gd->arch.tlb_addr和gd->relocaddr如图32.2.5.8所示:
图32.2.5.8 信息输出
从图32.2.5.8可以看出:
gd->arch.tlb_size= 0X4000 //MMU的TLB表大小
gd->arch.tlb_addr=0X9FFF0000 //MMU的TLB表起始地址,64KB对齐以后
gd->relocaddr=0X9FFF0000 //relocaddr地址
第47行,reserve_trace函数,留出跟踪调试的内存,I.MX6ULL没有用到!
第48行,reserve_uboot,留出重定位后的uboot所占用的内存区域,uboot所占用大小由gd->mon_len所指定,留出uboot的空间以后还要对gd->relocaddr做4K字节对齐,并且重新设置gd->start_addr_sp,结果如图32.2.5.9所示:
图32.2.5.9 信息输出
从图32.2.5.9可以看出:
gd->mon_len = 0XA8EF4
gd->start_addr_sp = 0X9FF47000
gd->relocaddr = 0X9FF47000
第49行,reserve_malloc,留出malloc区域,调整gd->start_addr_sp位置,malloc区域由宏TOTAL_MALLOC_LEN定义,宏定义如下:
#define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)
mx6ull_alientek_emmc.h文件中定义宏CONFIG_SYS_MALLOC_LEN为16MB=0X1000000,宏CONFIG_ENV_SIZE=8KB=0X2000,因此TOTAL_MALLOC_LEN=0X1002000。调整以后gd->start_addr_sp如图32.2.5.10所示:
图32.2.5.10 信息输出
从图32.2.5.10可以看出:
TOTAL_MALLOC_LEN=0X1002000
gd->start_addr_sp=0X9EF45000 //0X9FF47000-16MB-8KB=0X9EF45000
第50行,reserve_board函数,留出板子bd所占的内存区,bd是结构体bd_t,bd_t大小为80字节,结果如图32.2.5.11所示:
图32.2.5.10 信息输出
从图32.2.5.11可以看出:
gd->start_addr_sp=0X9EF44FB0
gd->bd=0X9EF44FB0
第51行,setup_machine,设置机器ID,linux启动的时候会和这个机器ID匹配,如果匹配的话linux就会启动正常。但是!!I.MX6ULL不用这种方式了,这是以前老版本的uboot和linux使用的,新版本使用设备树了,因此此函数无效。
第52行,reserve_global_data函数,保留出gd_t的内存区域,gd_t结构体大小为248B,结果如图32.2.5.11所示:
图32.2.5.11 信息输出
gd->start_addr_sp=0X9EF44EB8 //0X9EF44FB0-248=0X9EF44EB8
gd->new_gd=0X9EF44EB8
第53行,reserve_fdt,留出设备树相关的内存区域,I.MX6ULL的uboot没有用到,因此此函数无效。
第54行,reserve_arch是个空函数。
第55行,reserve_stacks,留出栈空间,先对gd->start_addr_sp减去16,然后做16字节对其。如果使能IRQ的话还要留出IRQ相应的内存,具体工作是由arch/arm/lib/stack.c文件中的函数arch_reserve_stacks完成。结果如图32.2.5.12所示:
图32.2.5.12 信息输出
在本uboot中并没有使用到IRQ,所以不会留出IRQ相应的内存区域,此时:
gd->start_addr_sp=0X9EF44E90
第56行,setup_dram_config函数设置dram信息,就是设置gd->bd->bi_dram[0].start和gd->bd->bi_dram[0].size,后面会传递给linux内核,告诉linux DRAM的起始地址和大小。结果如图32.2.5.13所示:
图32.2.5.13 信息输出
从图32.2.5.13可以看出,DRAM的起始地址为0X80000000,大小为0X20000000(512MB)。
第57行,show_dram_config函数,用于显示DRAM的配置,如图32.2.5.14所示:
图32.2.5.14 信息输出
第58行,display_new_sp函数,显示新的sp位置,也就是gd->start_addr_sp,不过要定义宏DEBUG,结果如图32.2.5.15所示:
图32.2.5.15 信息输出
图32.2.5.15中的gd->start_addr_sp值和我们前面分析的最后一次修改的值一致。
第60行,reloc_fdt函数用于重定位fdt,没有用到。
第61行,setup_reloc,设置gd的其他一些成员变量,供后面重定位的时候使用,并且将以前的gd拷贝到gd->new_gd处。需要使能DEBUG才能看到相应的信息输出,如图32.2.5.16所示:
图32.2.5.16 信息输出
从图32.2.5.16可以看出,uboot重定位后的偏移为0X18747000,重定位后的新地址为0X9FF4700,新的gd首地址为0X9EF44EB8,最终的sp为0X9EF44E90。
至此,board_init_f函数就执行完成了,最终的内存分配如图32.2.5.16所示:
图32.2.5.16 最终的内存分配图
32.2.6 relocate_code函数详解
relocate_code函数是用于代码拷贝的,此函数定义在文件arch/arm/lib/relocate.S中,代码如下:
示例代码32.2.6.1 relocate.S代码段
/*
* void relocate_code(addr_moni)
*
* This function relocates the monitor code.
*
* NOTE:
* To prevent the code below from containing references with an
* R_ARM_ABS32 relocation record type, we never refer to linker-
* defined symbols directly. Instead, we declare literals which
* contain their relative location with respect to relocate_code,
* and at run time, add relocate_code back to them.
*/
79 ENTRY( relocate_code)
80 ldr r1,= __image_copy_start /* r1 <- SRC &__image_copy_start */
81 subs r4, r0, r1 /* r4 <- relocation offset */
82 beq relocate_done /* skip relocation */
83 ldr r2,= __image_copy_end /* r2 <- SRC &__image_copy_end */
84
85 copy_loop:
86 ldmia r1!,{ r10- r11} /* copy from source address [r1] */
87 stmia r0!,{ r10- r11} /* copy to target address [r0] */
88 cmp r1, r2 /* until source end address [r2] */
89 blo copy_loop
90
91/*
92 * fix .rel.dyn relocations
93 */
94 ldr r2,= __rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
95 ldr r3,= __rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
96 fixloop:
97 ldmia r2!,{ r0- r1} /* (r0,r1) <- (SRC location,fixup) */
98 and r1, r1, #0xff
99 cmp r1, #23 /* relative fixup? */
100 bne fixnext
101
102/* relative fix: increase location by offset */
103 add r0, r0, r4
104 ldr r1,[ r0]
105 add r1, r1, r4
106 str r1,[ r0]
107 fixnext:
108 cmp r2, r3
109 blo fixloop
110
111 relocate_done:
112
113 #ifdef __XSCALE__
114/*
115 * On xscale, icache must be invalidated and write buffers
116 * drained, even with cache disabled - 4.2.7 of xscale core
117 developer's manual */
118 mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
119 mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
120 #endif
121
122/* ARMv4- don't know bx lr but the assembler fails to see that */
123
124 #ifdef __ARM_ARCH_4__
125 mov pc, lr
126 #else
127 bx lr
128 #endif
129
130 ENDPROC( relocate_code)
第80行,r1=__image_copy_start,也就是r1寄存器保存源地址,由表31.4.1.1可知,__image_copy_start=0X87800000。
第81行, r0=0X9FF47000,这个地址就是uboot拷贝的目标首地址。r4=r0-r1=0X9FF47000-0X87800000=0X18747000,因此r4保存偏移量。
第82行,如果在第81中,r0-r1等于0,说明r0和r1相等,也就是源地址和目的地址是一样的,那肯定就不需要拷贝了!执行relocate_done函数
第83行,r2=__image_copy_end,r2中保存拷贝之前的代码结束地址,由表31.4.1.1可知,__image_copy_end =0x8785dd54。
第84行,函数copy_loop完成代码拷贝工作!从r1,也就是__image_copy_start开始,读取uboot代码保存到r10和r11中,一次就只拷贝这2个32位的数据。拷贝完成以后r1的值会更新,保存下一个要拷贝的数据地址。
第87行,将r10和r11的数据写到r0开始的地方,也就是目的地址。写完以后r0的值会更新,更新为下一个要写入的数据地址。
第88行,比较r1是否和r2相等,也就是检查是否拷贝完成,如果不相等的话说明没有拷贝完成,没有拷贝完成的话就跳转到copy_loop接着拷贝,直至拷贝完成。
接下来的第94行~109行是重定位.rel.dyn段,.rel.dyn段是存放.text段中需要重定位地址的集合。重定位就是uboot将自身拷贝到DRAM的另一个地放去继续运行(DRAM的高地址处)。我们知道,一个可执行的bin文件,其链接地址和运行地址要相等,也就是链接到哪个地址,在运行之前就要拷贝到哪个地址去。现在我们重定位以后,运行地址就和链接地址不同了,这样寻址的时候不会出问题吗?为了分析这个问题,我们需要在mx6ull_alientek_emmc.c中输入如下所示内容:
示例代码32.2.6.2 mx6ull_alientek_emmc.c新添代码段
1staticint rel_a = 0;
2
3void rel_test( void)
4{
5 rel_a = 100;
6 printf( "rel_test\r\n");
7}
最后还需要在mx6ullevk.c文件中的board_init函数里面调用rel_test函数,否则rel_reset不会被编译进uboot。修改完成后的mx6ullevk.c如图32.2.6.1所示:
图32.2.6.1 加入rel测试相关代码
board_init函数会调用rel_test,rel_test会调用全局变量rel_a,使用如下命令编译uboot:
./mx6ull_alientek_emmc.sh
编译完成以后,使用arm-linux-gnueabihf-objdump将u-boot进行反汇编,得到u-boot.dis这个汇编文件,命令如下:
arm-linux-gnueabihf-objdump -D -m arm u-boot > u-boot.dis
在u-boot.dis文件中找到rel_a、rel_rest和board_init,相关内容如下所示:
示例代码32.2.6.3 汇编文件代码段
187804184< rel_test>:
287804184: e59f300c ldr r3,[ pc, #12] ; 87804198< rel_test+ 0x14>
387804188: e3a02064 mov r2, #100 ; 0x64
48780418c: e59f0008 ldr r0,[ pc, #8] ; 8780419c< rel_test+ 0x18>
587804190: e5832000 str r2,[ r3]
687804194: ea00d668 b 87839b3c< printf>
787804198: 8785da50;< UNDEFINED> instruction: 0x8785da50
88780419c: 878426a2 strhi r2,[ r4, r2, lsr #13]
9
10878041a0< board_init>:
11878041a0: e92d4010 push { r4, lr}
12878041a4: ebfffff6 bl 87804184< rel_test>
13
14......
15
168785da50< rel_a>:
178785da50: 00000000 andeq r0, r0, r0
第12行是borad_init调用rel_test函数,用到了bl指令,而bl指令时位置无关指令,bl指令是相对寻址的(pc+offset),因此uboot中函数调用是与绝对位置无关的。
再来看一下函数rel_test对于全局变量rel_a的调用,第2行设置r3的值为pc+12地址处的值,因为ARM流水线的原因,pc寄存器的值为当前地址+8,因此pc=0X87804184+8=0X8780418C,r3=0X8780418C+12=0X87804198,第7行就是0X87804198这个地址,0X87804198处的值为0X8785DA50。根据第17行可知,0X8785DA50正是变量rel_a的地址,最终r3=0X8785DA50。
第3行,r2=100。
第5行,将r2内的值写到r3地址处,也就是设置地址0X8785DA50的值为100,这不就是示例代码代码32.2.6.2中的第5行:rel_a = 100。
总结一下rel_a=100的汇编执行过程:
①、在函数rel_test末尾处有一个地址为0X87804198的内存空间(示例代码32.2.6.3第7行),此内存空间保存着变量rel_a的地址。
②、函数rel_test要想访问变量rel_a,首先访问末尾的0X87804198来获取变量rel_a的地址,而访问0X87804198是通过偏移来访问的,很明显是个位置无关的操作。
③、通过0X87804198获取到变量rel_a的地址,对变量rel_a进行操作。
④、可以看出,函数rel_test对变量rel_a的访问没有直接进行,而是使用了一个第三方偏移地址0X87804198,专业术语叫做Label。这个第三方偏移地址就是实现重定位后运行不会出错的重要原因!
uboot重定位后偏移为0X18747000,那么重定位后函数rel_test的首地址就是0X87804184+0X18747000=0X9FF4B184。保存变量rel_a地址的Label就是0X9FF4B184+8+12=0X9FF4B198(既:0X87804198+0X18747000),变量rel_a的地址就为0X8785DA50+0X18747000=0X9FFA4A50。重定位后函数rel_test要想正常访问变量rel_a就得设置0X9FF4B198(重定位后的Label)地址出的值为0X9FFA4A50(重定位后的变量rel_a地址)。这样就解决了重定位后链接地址和运行地址不一致的问题。
可以看出,uboot对于重定位后链接地址和运行地址不一致的解决方法就是采用位置无关码,在使用ld进行链接的时候使用选项“-pie”生成位置无关的可执行文件。在文件arch/arm/config.mk下有如下代码:
示例代码32.2.6.4 config.mk文件代码段
82 # needed for relocation
83 LDFLAGS_u- boot +=- pie
第83行就是设置uboot链接选项,加入了“-pie”选项,编译链接uboot的时候就会使用到“-pie”,如图32.2.6.2所示:
图32.2.6.2 链接命令
使用“-pie”选项以后会生成一个.rel.dyn段,uboot就是靠这个.rel.dyn来解决重定位问题的,在u-bot.dis的.rel.dyn段中有如下所示内容:
示例代码32.2.6.5 .rel.dyn段代码段
1 Disassembly of section . rel. dyn:
2
38785da44< __rel_dyn_end- 0x8ba0>:
48785da44: 87800020 strhi r0,[ r0, r0, lsr #32]
58785da48: 00000017 andeq r0, r0, r7, lsl r0
6......
78785dfb4: 87804198;< UNDEFINED> instruction: 0x87804198
88785dfb8: 00000017 andeq r0, r0, r7, lsl r0
先来看一下.rel.dyn段的格式,类似第7行和第8行这样的是一组,也就是两个4字节数据为一组。高4字节是Label地址标识0X17,低4字节就是Label的地址,首先判断Label地址标识是否正确,也就是判断高4字节是否为0X17,如果是的话高4字节就是Label值。
第7行值为0X87804198,第8行为0X00000017,说明第7行的0X87804198是个Label,这个正是示例代码32.2.6.3中存放变量rel_a地址的那个Label。根据前面的分析,只要将地址0X87804198+offset处的值改为重定位后的变量rel_a地址即可。我们猜测的是否正确,看一下uboot对.rel.dyn段的重定位即可(示例代码代码32.2.6.1中的第94~109行),.rel.dyn段的重定位代码如下:
示例代码32.2.6.6 relocate.S代码段
91/*
92 * fix .rel.dyn relocations
93 */
94 ldr r2,= __rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
95 ldr r3,= __rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
96 fixloop:
97 ldmia r2!,{ r0- r1} /* (r0,r1) <- (SRC location,fixup) */
98 and r1, r1, #0xff
99 cmp r1, #23 /* relative fixup? */
100 bne fixnext
101
102/* relative fix: increase location by offset */
103 add r0, r0, r4
104 ldr r1,[ r0]
105 add r1, r1, r4
106 str r1,[ r0]
107 fixnext:
108 cmp r2, r3
109 blo fixloop
第94行,r2=__rel_dyn_start,也就是.rel.dyn段的起始地址。
第95行,r3=__rel_dyn_end,也就是.rel.dyn段的终止地址。
第97行,从.rel.dyn段起始地址开始,每次读取两个4字节的数据存放到r0和r1寄存器中,r0存放低4字节的数据,也就是Label地址;r1存放高4字节的数据,也就是Label标志。
第98行,r1中给的值与0xff进行与运算,其实就是取r1的低8位。
第99行,判断r1中的值是否等于23(0X17)。
第100行,如果r1不等于23的话就说明不是描述Label的,执行函数fixnext,否则的话继续执行下面的代码。
第103行,r0保存着Label值,r4保存着重定位后的地址偏移,r0+r4就得到了重定位后的Label值。此时r0保存着重定位后的Label值,相当于0X87804198+0X18747000=0X9FF4B198。
第104,读取重定位后Label所保存的变量地址,此时这个变量地址还是重定位前的(相当于rel_a重定位前的地址0X8785DA50),将得到的值放到r1寄存器中。
第105行,r1+r4即可得到重定位后的变量地址,相当于rel_a重定位后的0X8785DA50+0X18747000=0X9FFA4A50。
第106行,重定位后的变量地址写入到重定位后的Label中,相等于设置地址0X9FF4B198处的值为0X9FFA4A50。
第108行,比较r2和r3,查看.rel.dyn段重定位是否完成。
第109行,如果r2和r3不相等,说明.rel.dyn重定位还未完成,因此跳到fixloop继续重定位.rel.dyn段。
可以看出,uboot中对.rel.dyn段的重定位方法和我们猜想的一致。.rel.dyn段的重定位比较复杂一点,有点绕,因为涉及到链接地址和运行地址的问题。
32.2.7 relocate_vectors函数详解
函数relocate_vectors用于重定位向量表,此函数定义在文件函数源码如下:
示例代码32.2.7.1 relocate.S代码段
27 ENTRY( relocate_vectors)
28
29 #ifdef CONFIG_CPU_V7M
30 /*
31 * On ARMv7-M we only have to write the new vector address
32 * to VTOR register.
33 */
34 ldr r0,[ r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
35 ldr r1,= V7M_SCB_BASE
36 str r0,[ r1, V7M_SCB_VTOR]
37 #else
38 #ifdef CONFIG_HAS_VBAR
39 /*
40 * If the ARM processor has the security extensions,
41 * use VBAR to relocate the exception vectors.
42 */
43 ldr r0,[ r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
44 mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
45 #else
46 /*
47 * Copy the relocated exception vectors to the
48 * correct address
49 * CP15 c1 V bit gives us the location of the vectors:
50 * 0x00000000 or 0xFFFF0000.
51 */
52 ldr r0,[ r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
53 mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */
54 ands r2, r2, #( 1<< 13)
55 ldreq r1,= 0x00000000 /* If V=0 */
56 ldrne r1,= 0xFFFF0000 /* If V=1 */
57 ldmia r0!,{ r2- r8, r10}
58 stmia r1!,{ r2- r8, r10}
59 ldmia r0!,{ r2- r8, r10}
60 stmia r1!,{ r2- r8, r10}
61 #endif
62 #endif
63 bx lr
64
65 ENDPROC( relocate_vectors)
第29行,如果定义了CONFIG_CPU_V7M的话就执行第30~36行的代码,这是Cortex-M内核单片机执行的语句,因此对于I.MX6ULL来说是无效的。
第38行,如果定义了CONFIG_HAS_VBAR的话就执行此语句,这个是向量表偏移,Cortex-A7是支持向量表偏移的。而且,在.config里面定义了CONFIG_HAS_VBAR,因此会执行这个分支。
第43行,r0=gd->relocaddr,也就是重定位后uboot的首地址,向量表肯定是从这个地址开始存放的。
第44行,将r0的值写入到CP15的VBAR寄存器中,也就是将新的向量表首地址写入到寄存器VBAR中,设置向量表偏移。
相关问答
uboot 是一个操作系统内核还是一段引导程序?是一段引导程序。1,uboot是用来干什么的,有什么作用?uboot属于bootloader的一种,是用来引导启动内核的,它的最终目的就是,从flash中读出内核,放到内存...u...
cpu的spl是什么?SPLSPL是uboot第一阶段执行的代码.主要负责搬移uboot第二阶段的代码到内存中运行.SPL是由固化在芯片内部的ROM引导的.我们知道很多芯片厂商固化的ROM支持...
linux的 uboot 启动映像,zImage和uImage的区别[最佳回答]zImage是ARMLinux常用的一种压缩映像文件,uImage是U-boot专用的映像文件,它是在zImage之前加上一个长度为0x40的“头”,说明这个映像文件的类型、...
想做一个硬件工程师,不知道怎么开始?2周,...2、PCB布板布线:4周,包括配合结构、PCB进行电路调整或者器件重新选型。3、PCB厂家制板:2周,这两周是最闲的,发板同时必须完成BOM上传,这个不能忘。多...
请教:WR743N改了16M的flash怎么从openwrt中编译出来16M固件?首先修改trunk/tools/firmware-utils/src/mktplinkfw.c只修改fw_max_len为0xfc0000,16Mflash;只修改fw_max_len为0x7c00...
帮个忙童鞋们!有谁知道么 台湾系统安全可靠性测试方法,系...[回答]方法如下:1PC端打开minicom,在arm板上写一个发送程序2在uboot下设置启动参数,把ttyAMA1映射成系统的监视端口。感觉国可工软价格还是挺优惠的,获得...
网络电视显示IPTV无法运行怎么办_土巴兔装修问答按下UBOOT按钮,然后专题为什么电视显示无信号源电视在我们生活中是非常常见...以合理的颜色和大小显示在电视上,所以电视机显像管的选择以及后期安装使用方面...
灵云(型号:Q3)网络电视机顶盒不能看电视了,对设备肿么操作才...这个刷机,需要打开机器,按下UBOOT按钮,然后找到合适的固件。通过线刷。。。如需相关服务请联系287263573有用(0)回复hsxylcs别升级,用u盘装app就行啦...
谁了解!如何联系免拆洗硅藻土过滤机哪里有名,免拆洗硅藻土...[回答]硅藻土过滤机,不用再加臭氧消毒设备,可到国家泳池水质检测标准CJ244-2007。砂缸过滤设备必须添加相应的消毒设备(臭氧消毒、紫外线消毒、铜...砂缸...