之前我有分析过基于Cortex-M单片机中BootLoader的严谨实现详解,那在Cortex-A核中的U-Boot做了什么事呢?比如在M核中向量表的地址就直接在一个系统寄存器中设置,而在A核中向量表在CP15协处理器中进行设置,所以需要触发一个异常,在特权模式下进行设置。上一节中分析了U-Boot上电后进行初始化系统的start.S
文件中的代码重定位,我们就接着分析后面的系统寄存器的初始化。
文章目录
1 上电代码执行流程
在上一节u-boot.lds链接脚本分析中,我们知道程序的最开始是vector.S
,我们看一下开头的代码:
也就是程序一开始,我们就跳转到reset
标号处执行,这个标号在start.S
文件中定义:
完整代码如下:
reset:
/* Allow the board to save important registers */
b save_boot_params
save_boot_params_ret:
#ifdef CONFIG_POSITION_INDEPENDENT
/*
* Fix .rela.dyn relocations. This allows U-Boot to loaded to and
* executed at a different address than it was linked at.
*/
pie_fixup:
adr r0, reset /* r0 <- Runtime value of reset label */
ldr r1, =reset /* r1 <- Linked value of reset label */
subs r4, r0, r1 /* r4 <- Runtime-vs-link offset */
beq pie_fixup_done
adr r0, pie_fixup
ldr r1, _rel_dyn_start_ofs
add r2, r0, r1 /* r2 <- Runtime &__rel_dyn_start */
ldr r1, _rel_dyn_end_ofs
add r3, r0, r1 /* r3 <- Runtime &__rel_dyn_end */
pie_fix_loop:
ldr r0, [r2] /* r0 <- Link location */
ldr r1, [r2, #4] /* r1 <- fixup */
cmp r1, #23 /* relative fixup? */
bne pie_skip_reloc
/* relative fix: increase location by offset */
add r0, r4
ldr r1, [r0]
add r1, r4
str r1, [r0]
str r0, [r2]
add r2, #8
pie_skip_reloc:
cmp r2, r3
blo pie_fix_loop
pie_fixup_done:
#endif
#ifdef CONFIG_ARMV7_LPAE
/*
* check for Hypervisor support
*/
mrc p15, 0, r0, c0, c1, 1 @ read ID_PFR1
and r0, r0, #CPUID_ARM_VIRT_MASK @ mask virtualization bits
cmp r0, #(1 << CPUID_ARM_VIRT_SHIFT)
beq switch_to_hypervisor
switch_to_hypervisor_ret:
#endif
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
mrs r0, cpsr
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0
/*
* Setup vector:
* (OMAP4 spl TEXT_BASE is not 32 byte aligned.
* Continue to use ROM code vector only in OMAP4 spl)
*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register
#ifdef CONFIG_HAS_VBAR
/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif
#endif
/* the mask ROM code should have PLL and others stable */
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT)
#ifdef CONFIG_CPU_V7A
bl cpu_init_cp15
#endif
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT_ONLY)
bl cpu_init_crit
#endif
#endif
bl _main
这篇文章就来分析一下这一部分的代码。
2 save_boot_params和位置无关码
一开始就使用b
直接跳转到标号save_boot_params
了,这里使用的是b
而不是bl
也就是不会返回了。但我们发现save_boot_params
就在当前文件中定义:
这边又跳到save_boot_params_ret
,即正好是reset
的下一行代码。所以这个应该是当我们需要保存一些boot参数的时候,可以通过save_boot_params
函数实现。
接下来如果定义了CONFIG_POSITION_INDEPENDENT
宏定义,就会执行代码重定位的操作。
实际上,这种代码重定位一般用在比较老的芯片中,比如S3C2440,系统上电后会从Flash中读取4KB的代码到内置的大小为4KB的SRAM中。但我们的U-Boot的完整代码不止4KB,那我们就需要在这4KB的代码中实现将剩余的代码手动拷贝到更大的SDRAM中。在拷贝完后便出现了变量地址不一致、位置无关代码等问题。
我目前使用的是I.MX6ULL,在上电后ROM BootLoader就帮我们完整地拷贝到RAM中了,所以我们在U-Boot目录下的.config
文件中就没有定义这个宏定义。另外,这种实现已经过时了,在后面的U-Boot源码中有其它重定位的方式,所以这部分代码就不分析了。
3 LPAE(Large Physical Address Extensions)
如果使能了CONFIG_ARMV7_LPAE
的话,将执行相关的代码。
3.1 ARMv7的LPAE特性
LPAE(Large Physical Address Extension
,长物理地址扩展)是ARMv7-A的一个特性,他可以将32位虚拟内存地址转换为40位物理内存地址,将可访问的物理地址范围从4GB扩展到1TB。
当运行虚拟机时,虚拟化扩展提供了额外的第二阶段地址转换。该翻译的第一阶段生成中间物理地址(Intermediate Physical Address
,IPA
),而第二阶段则生成物理地址。这个转换过程的第二阶段由Hypervisor
控制,TLB条目还可以具有与ASID(Address Space ID
)一样的虚拟机ID。同样,可以禁用第二阶段的MMU,并实现从IPA到PA的平面映射。如下图所示:
- 详细请参考ARM Cortex-A Series Programmer’s Guide的
22.4 Large Physical Address Extensions
3.2 代码分析
读协处理器的语句为:
MRC{cond} p15, <opcode_1>, <rd>, <CRn>, <CRm>, <opcode_2>
mrc p15, 0, r0, c0, c1, 1
对应手册中的:
也就是把ID_PFR1
读到r0
寄存器中。
#define CPUID_ARM_VIRT_SHIFT 12
#define CPUID_ARM_VIRT_MASK (0xF << CPUID_ARM_VIRT_SHIFT)
后面的and
与操作和cmp
比较操作实际上就是判断ID_PFR1
的bit12是否为1,若为1则表示支持虚拟化。
如果支持虚拟化,则后面的代码beq switch_to_hypervisor
就会跳转到switch_to_hypervisor
标签处,切换到Hypervisor
模式以支持长地址。
- 实际上U-Boot没有帮我们实现
switch_to_hypervisor
,这需要我们自己实现切换到Hypervisor
特权等级。然后跳转回下面的标号switch_to_hypervisor_ret
。
4 特权模式切换、关闭中断
继续往下看代码,从注释可以看出,这段代码将禁用FIQ
和IRQ
,然后设置CPU为SVC特权模式。
前两行读出CPSR
寄存器的低5位并保存在r1
中,来看一下这5位的定义:
接着我们用teq
指令判断模式位是否为0x1a,即Hypervisor模式。
继续往下看代码:
-
bicne r0, r0, #0x1f
: 如果前面的测试结果不相等,则将r0中低5位的模式位清空bicne
:BIt Clear if Not Equal
-
orrne r0, r0, #0x13
: 如果前面的测试结果不相等,则将r0的模式位设置为SVC模式orrne
:OR if Not Equal
-
orr r0, r0, #0xc0
: 使用按位或指令,将r0的IRQ和FIQ位设置为1,即禁用IRQ和FIQ
msr cpsr, r0
: 将r0的值写回CPSR寄存器,完成模式切换和中断禁用。
5 设置异常向量表的地址
如果未定义宏CONFIG_OMAP44XX
且未定义宏CONFIG_SPL_BUILD
,则执行下面的代码块,主要是设置处理器的异常向量地址,其中包含一些与处理器控制和向量基地址相关的操作。
CONFIG_OMAP44XX
:当前使用的是OMAP4系列芯片。即该芯片不支持这些配置CONFIG_SPL_BUILD
:SPL(Secondary Program Loader
)是用于启动U-Boot的第一阶段程序,负责完成一些基本的硬件初始化、加载主引导加载器、设置环境变量等任务。也就是说,如果有SPL了,在SPL中已经帮我们完成了这些代码了,就不用再执行了。- 具体参考U-Boot目录下的
doc/README.SPL
文档
- 具体参考U-Boot目录下的
现在来看一下代码,前三行就是读CP15中的SCTLR
寄存器中的值,然后将其的CR_V
位清零,即bit13位:
#define CR_V (1 << 13)
我们来看一下SCTLR
的第13位:
从手册描述可以知道,将该位设置为0,即将异常向量表放置在低地址,这样处理器在发生异常时会跳转到低地址处执行相应的异常处理程序,还可以通过重新映射来改变异常向量表的实际物理地址。
接下来我们将_start
标号的地址赋给r0
,从下图可以看到_start
就是我们程序向量表的地址,从上一节u-boot.lds链接脚本分析分析可知,这个地址就是程序的链接地址。
然后将这个地址设置给向量表寄存器,在Cortex-A7中,向量表寄存器即CP15的c15
寄存器中的CBAR
寄存器。
6 CPU初始化
下面的代码在SKIP_LOWLEVEL_INIT
关闭时执行,如果定义了CONFIG_CPU_V7A
则执行cpu_init_cp15
,如果没有使能SKIP_LOWLEVEL_INIT_ONLY
则执行cpu_init_crit
。在我使用的U-Boot配置中,这里的所有情况都满足,所以会执行这两个函数。
6.1 cpu_init_cp15
这段代码很长,主要是对CP15所有必要的寄存器做一个初始化。
ENTRY(cpu_init_cp15)
#if CONFIG_IS_ENABLED(ARMV7_SET_CORTEX_SMPEN)
/*
* The Arm Cortex-A7 TRM says this bit must be enabled before
* "any cache or TLB maintenance operations are performed".
*/
mrc p15, 0, r0, c1, c0, 1 @ read auxilary control register
orr r0, r0, #1 << 6 @ set SMP bit to enable coherency
mcr p15, 0, r0, c1, c0, 1 @ write auxilary control register
#endif
/*
* Invalidate L1 I/D
*/
mov r0, #0 @ set up for MCR
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array
mcr p15, 0, r0, c7, c10, 4 @ DSB
mcr p15, 0, r0, c7, c5, 4 @ ISB
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
#if CONFIG_IS_ENABLED(SYS_ICACHE_OFF)
bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
#else
orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache
#endif
mcr p15, 0, r0, c1, c0, 0
#ifdef CONFIG_ARM_ERRATA_716044
mrc p15, 0, r0, c1, c0, 0 @ read system control register
orr r0, r0, #1 << 11 @ set bit #11
mcr p15, 0, r0, c1, c0, 0 @ write system control register
#endif
#if (defined(CONFIG_ARM_ERRATA_742230) || defined(CONFIG_ARM_ERRATA_794072))
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 4 @ set bit #4
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_743622
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 6 @ set bit #6
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_751472
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 11 @ set bit #11
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_761320
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 21 @ set bit #21
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_845369
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 22 @ set bit #22
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
mov r5, lr @ Store my Caller
mrc p15, 0, r1, c0, c0, 0 @ r1 has Read Main ID Register (MIDR)
mov r3, r1, lsr #20 @ get variant field
and r3, r3, #0xf @ r3 has CPU variant
and r4, r1, #0xf @ r4 has CPU revision
mov r2, r3, lsl #4 @ shift variant field for combined value
orr r2, r4, r2 @ r2 has combined CPU variant + revision
/* Early stack for ERRATA that needs into call C code */
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr r0, =(CONFIG_SPL_STACK)
#else
ldr r0, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
mov sp, r0
#ifdef CONFIG_ARM_ERRATA_798870
cmp r2, #0x30 @ Applies to lower than R3p0
bge skip_errata_798870 @ skip if not affected rev
cmp r2, #0x20 @ Applies to including and above R2p0
blt skip_errata_798870 @ skip if not affected rev
mrc p15, 1, r0, c15, c0, 0 @ read l2 aux ctrl reg
orr r0, r0, #1 << 7 @ Enable hazard-detect timeout
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_l2aux_ctrl
isb @ Recommended ISB after l2actlr update
pop {r1-r5} @ Restore the cpu info - fall through
skip_errata_798870:
#endif
#ifdef CONFIG_ARM_ERRATA_801819
cmp r2, #0x24 @ Applies to lt including R2p4
bgt skip_errata_801819 @ skip if not affected rev
cmp r2, #0x20 @ Applies to including and above R2p0
blt skip_errata_801819 @ skip if not affected rev
mrc p15, 0, r0, c0, c0, 6 @ pick up REVIDR reg
and r0, r0, #1 << 3 @ check REVIDR[3]
cmp r0, #1 << 3
beq skip_errata_801819 @ skip erratum if REVIDR[3] is set
mrc p15, 0, r0, c1, c0, 1 @ read auxilary control register
orr r0, r0, #3 << 27 @ Disables streaming. All write-allocate
@ lines allocate in the L1 or L2 cache.
orr r0, r0, #3 << 25 @ Disables streaming. All write-allocate
@ lines allocate in the L1 cache.
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_acr
pop {r1-r5} @ Restore the cpu info - fall through
skip_errata_801819:
#endif
#ifdef CONFIG_ARM_CORTEX_A15_CVE_2017_5715
mrc p15, 0, r0, c1, c0, 1 @ read auxilary control register
orr r0, r0, #1 << 0 @ Enable invalidates of BTB
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_acr
pop {r1-r5} @ Restore the cpu info - fall through
#endif
#ifdef CONFIG_ARM_ERRATA_454179
mrc p15, 0, r0, c1, c0, 1 @ Read ACR
cmp r2, #0x21 @ Only on < r2p1
orrlt r0, r0, #(0x3 << 6) @ Set DBSM(BIT7) and IBE(BIT6) bits
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_acr
pop {r1-r5} @ Restore the cpu info - fall through
#endif
#if defined(CONFIG_ARM_ERRATA_430973) || defined (CONFIG_ARM_CORTEX_A8_CVE_2017_5715)
mrc p15, 0, r0, c1, c0, 1 @ Read ACR
#ifdef CONFIG_ARM_CORTEX_A8_CVE_2017_5715
orr r0, r0, #(0x1 << 6) @ Set IBE bit always to enable OS WA
#else
cmp r2, #0x21 @ Only on < r2p1
orrlt r0, r0, #(0x1 << 6) @ Set IBE bit
#endif
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_acr
pop {r1-r5} @ Restore the cpu info - fall through
#endif
#ifdef CONFIG_ARM_ERRATA_621766
mrc p15, 0, r0, c1, c0, 1 @ Read ACR
cmp r2, #0x21 @ Only on < r2p1
orrlt r0, r0, #(0x1 << 5) @ Set L1NEON bit
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_acr
pop {r1-r5} @ Restore the cpu info - fall through
#endif
#ifdef CONFIG_ARM_ERRATA_725233
mrc p15, 1, r0, c9, c0, 2 @ Read L2ACR
cmp r2, #0x21 @ Only on < r2p1 (Cortex A8)
orrlt r0, r0, #(0x1 << 27) @ L2 PLD data forwarding disable
push {r1-r5} @ Save the cpu info registers
bl v7_arch_cp15_set_l2aux_ctrl
pop {r1-r5} @ Restore the cpu info - fall through
#endif
#ifdef CONFIG_ARM_ERRATA_852421
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 24 @ set bit #24
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_852423
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 12 @ set bit #12
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
mov pc, r5 @ back to my caller
ENDPROC(cpu_init_cp15)
这里就不把每个CP15寄存器的描述都截图上来了。简单总结一下:
这段代码主要完成了一些与 ARM Cortex-A 系列处理器相关的低级别初始化工作,包括设置协处理器寄存器、清除缓存、禁用 MMU(内存管理单元)等。下面是对代码的主要功能进行的总结:
- 检查并设置SMP位:
- 通过读取和修改辅助控制寄存器(
auxilary control register
)的第6位,设置SMP位(Symmetric Multi-Processing
),以启用协同性。
- 通过读取和修改辅助控制寄存器(
- 清除L1 I/D Cache和TLBs:
- 使用协处理器指令
mcr
,对 L1 I/D 缓存进行清除。 - 对TLBs进行清除,确保缓存和TLBs不包含任何旧的数据。
- 使用协处理器指令
- 禁用MMU和缓存
- 通过读取和修改系统控制寄存器,清除与MMU相关的位(位13,即–V-)、清除缓存相关的位(位0~2),同时设置一些其他位,以禁用MMU和缓存。
- 处理 ARM 处理器的错误
- 根据 ARM 处理器的具体型号和可能的错误,进行相应的错误处理。
- 这涉及到一系列的条件判断和配置,包括设置协处理器寄存器、修改控制寄存器等。
- 设置堆栈指针SP
- 为了处理一些Errata(硬件或软件设计中发现的错误、缺陷或问题的列表)需要调用C代码的情况,设置堆栈指针
sp
。
- 为了处理一些Errata(硬件或软件设计中发现的错误、缺陷或问题的列表)需要调用C代码的情况,设置堆栈指针
- 执行ARM处理器的Errata补丁
- 根据处理器的具体型号和可能的Errata,执行相应的Errata补丁。
- 这包括设置协处理器寄存器、修改控制寄存器等,以修复或绕过处理器的缺陷。
6.2 cpu_init_crit
代码如下:
ENTRY(cpu_init_crit)
b lowlevel_init @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)
实际上就是调用lowlevel_init
函数(在/arch/arm/cpu/armv7/lowlevel_init.S
中),为了方便阅读,我把不满足的宏定义分支删掉了:
WEAK(lowlevel_init)
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, #7
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
push {ip, lr}
bl s_init
pop {ip, pc}
ENDPROC(lowlevel_init)
相关的宏定义如下:
#define GD_SIZE 224
#define IRAM_BASE_ADDR 0x00900000
#define IRAM_SIZE 0x00020000
#define GENERATED_GBL_DATA_SIZE 240
#define CONFIG_SYS_INIT_RAM_ADDR IRAM_BASE_ADDR
#define CONFIG_SYS_INIT_RAM_SIZE IRAM_SIZE
#define CONFIG_SYS_INIT_SP_OFFSET \
(CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)
#define CONFIG_SYS_INIT_SP_ADDR \
(CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)
CONFIG_SYS_INIT_SP_ADDR
算出来就是:0x00900000+0x00020000-240=0x91FF10
,即
#define CONFIG_SYS_INIT_SP_ADDR 0x91FF10
0x00900000
是芯片内置RAM的地址,大小为0x20000=128KB。
现在再回来分析一下前面的汇编代码,这段代码比较简单,主要就是设置一下堆栈指针,然后调用s_init
。
此时SP的值为0x91FF10-224=0x91FE30
,如果栈是向下增长的话,也就是说,最前面留了240B作为GENERATED_GBL_DATA_SIZE
,又留了224B作为GD_SIZE
,至于这有什么用途,这里就不详细分析了,总之就是为这两个数据结构保留了栈空间。
6.2.1 s_init
这个函数就对应到厂商的芯片初始化代码中了。由于硬件限制,在MX6Q或其它的芯片中我们需要对所有PFD进行门控/取消门控,以确保PFD正常工作,否则,在复位后,PFD可能不会输出时钟。由于我这里使用的是I.MX6ULL,所以这个函数会直接返回,什么都不用执行。
void s_init(void)
{
struct anatop_regs *anatop = (struct anatop_regs *)ANATOP_BASE_ADDR;
struct mxc_ccm_reg *ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
u32 mask480;
u32 mask528;
u32 reg, periph1, periph2;
#if defined(CONFIG_ANDROID_SUPPORT)
/* Enable RTC */
writel(0x21, 0x020cc038);
#endif
if (is_mx6sx() || is_mx6ul() || is_mx6ull() || is_mx6sll())
return;
......
}
7 总结
最后程序就跳转到_main
(在arch/arm/lib/crt0.S
中)函数执行。我们知道U-Boot最后就是执行一个命令行程序,然后可以跳转到内核执行。这正是在_main
中要完成的操作。下一篇文章我们继续分析。