当前位置:   article > 正文

ARM uboot源码分析3-启动第一阶段

arm uboot

一、start.S 解析7

总结回顾:lowlevel_init.S 中总共做了哪些事情:
检查复位状态、IO 恢复、关看门狗、开发板供电锁存、时钟初始化、DDR 初始化、串口初始化并打印 ‘O’、tzpc 初始化、打印 ‘K’。

在这里插入图片描述

其中值得关注的:关看门狗、开发板供电锁存、时钟初始化、DDR初始化、打印"OK"。


1、再次设置栈(DDR 中的栈)

在这里插入图片描述

(1) 再次开发板供电锁存。第一,做 2 次供电锁存是不会错的;第二,做 2 次则第 2 次无意义;做代码移植时有一个古怪谨慎保守策略:就是尽量添加代码而不要删除代码。


在这里插入图片描述

(2) 之前在调用 lowlevel_init 程序前,设置过 1 次栈(start.S 284-287行),那时候因为 DDR 尚未初始化,因此程序执行都是在 SRAM 中,所以在 SRAM 中分配了一部分内存作为栈。

本次因为 DDR 已经被初始化了,因此要把栈挪移到 DDR 中,所以要重新设置栈,这是第二次(start.S 297-299 行);这里实际设置的栈的地址是 33E00000,刚好在 uboot 的代码段的下面紧挨着。

在这里插入图片描述

在这里插入图片描述


(3) 为什么要再次设置栈?因为 DDR 已经初始化了,已经有大片内存可以用了,没必要再把栈放在 SRAM 中可怜兮兮的了;原来 SRAM 中内存大小空间有限,栈放在那里要注意不能使用过多的栈,否则栈会溢出,我们及时将栈迁移到 DDR 中,也是为了尽可能避免栈使用时候的小心翼翼。

感慨:uboot 的启动阶段主要技巧就在于,小范围内有限条件下的辗转腾挪。


2、再次判断当前地址以决定是否重定位

在这里插入图片描述

(1) 再次用相同的代码判断运行地址是在 SRAM 中还是 DDR 中,不过本次判断的目的不同(上次判断是为了决定是否要执行初始化时钟和 DDR 的代码),这次判断是为了决定是否进行 uboot 的 relocate 重定向

(2) 冷启动时,当前情况是:uboot 的前一部分(16kb 或者 8kb)开机自动从 SD 卡加载到 SRAM 中正在运行,uboot 的第二部分(其实第二部分是整个 uboot 文件)还躺在 SD 卡的某个扇区开头的 N 个扇区中。此时 uboot 的第一阶段已经即将结束了(第一阶段该做的事基本做完了),结束之前要把第二部分(整个 uboot 文件)加载到 DDR 中链接地址处(0x33e00000),这个加载过程就叫重定位。


二、uboot 重定位详解

(1) 0xD0037488 这个内存地址在 SRAM 中,这个地址中的值是被硬件自动设置的。硬件根据我们实际电路中 SD 卡在哪个通道中,会将这个地址中的值设置为相应的数字。譬如我们从 SD0 通道启动时,这个值为 EB000000 ;从 SD2 通道启动时,这个值为 EB200000

《S5PV210_iROM_ApplicationNote_Preliminary_20091126.pdf》:

在这里插入图片描述


《S5PV210_UM_REV1.1.pdf》:
在这里插入图片描述


(2) 我们在 start.S 的 260 行确定了从 MMCSD 启动,然后又在 278 行将 #BOOT_MMCSD 写入了 INF_REG3 寄存器中存储着。然后又在 322 行读出来,再和 #BOOT_MMCSD 去比较,确定是从 MMCSD 启动。最终跳转到 mmcsd_boot 函数中去执行重定位动作。

在这里插入图片描述
在这里插入图片描述


(3) 真正的重定位是通过调用 movi_bl2_copy 函数完成的,在 uboot/cpu/s5pc11x/movi.c 中。是一个 C 语言的函数。
在这里插入图片描述


(4) copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT, CFG_PHY_UBOOT_BASE, 0);
分析参数:2 表示通道 2;MOVI_BL2_POS 是 uboot 的第二部分在 SD 卡中的开始扇区,这个扇区数字必须和烧录 uboot 时烧录的位置相同;MOVI_BL2_BLKCNT 是 uboot 的长度占用的扇区数;CFG_PHY_UBOOT_BASE 是重定位时将 uboot 的第二部分复制到 DDR 中的起始地址(33E00000)。

在这里插入图片描述

在这里插入图片描述

《S5PV210_iROM_ApplicationNote_Preliminary_20091126.pdf》:

在这里插入图片描述


三、start.S 解析8

1、什么是虚拟地址、物理地址

(1) 物理地址就是物理设备设计生产时赋予的地址。像裸机中使用的寄存器的地址就是 CPU 设计时指定的,这个就是物理地址。物理地址是硬件编码的,是设计生产时确定好的,一旦确定了就不能改了。

(2) 一个事实就是:寄存器的物理地址是无法通过编程修改的,是多少就是多少,只能通过查询数据手册获得并操作。坏处就是不够灵活。一个解决方案就是使用虚拟地址。

(3) 虚拟地址意思就是,在我们软件操作和硬件被操作之间增加一个层次,叫做虚拟地址映射层。有了虚拟地址映射后,软件操作只需要给虚拟地址硬件操作还是用原来的物理地址映射层建立一个虚拟地址到物理地址的映射表。当我们软件运行的时候,软件中使用的虚拟地址在映射表中查询,得到对应的物理地址再发给硬件去执行(虚拟地址到物理地址的映射是不可能通过软件来实现的)。

在这里插入图片描述


2、MMU 单元的作用

(1) MMU 就是 memory management unit,内存管理单元。MMU实际上是 SoC 中一个硬件单元,它的主要功能就是实现虚拟地址到物理地址的映射

(2) MMU 芯片在 CP15 协处理器中进行控制,也就是说要操控 MMU 进行虚拟地址映射,方法就是对 cp15 协处理器的寄存器进行编程。


3、地址映射的额外收益1:访问控制

(1) 访问控制就是:在管理上对内存进行分块,然后每块进行独立的虚拟地址映射,然后在每一块的映射关系中同时还实现了访问控制(对该块可读、可写、只读、只写、不可访问等控制)。

(2) 回想在 C 语言中编程中经常会出现一个错误:Segmentation fault。实际上这个段错误就和 MMU 实现的访问控制有关。当前程序只能操作自己有权操作的地址范围(若干个内存块),如果当前程序指针出错访问了不该访问的内存块,则就会触发段错误。


4、地址映射的额外收益2:cache

(1) cache 的工作和虚拟地址映射有关系。

(2) cache 是快速缓存,意思就是比 CPU 慢但是比 DDR 块。CPU 嫌 DDR 太慢了,于是乎把一些 DDR 中常用的内容事先读取缓存在 cache 中,然后 CPU 每次需要找东西时先在 cache 中找。如果 cache 中有就直接用 cache 中的;如果 cache 中没有,才会去 DDR 中寻找。

参考阅读


四、start.S 解析9

在这里插入图片描述

1、使能域访问(cp15的c3寄存器)

(1) cp15协处理器内部有 c0 到 c15 共 16 个寄存器,这些寄存器每一个都有自己的作用。我们通过 mrc 和 mcr 指令来访问这些寄存器。所谓的操作 cp 协处理器其实就是操作 cp15 的这些寄存器。

(2) c3 寄存器在 mmu 中的作用是控制域访问。域访问是和 MMU 的访问控制有关的。


2、设置 TTB(cp15 的 c2 寄存器)

(1) TTB 就是 translation table base,转换表基地址。首先要明白什么是 TT(translation table转换表),TTB其实就是转换表的基地址。

(2) 转换表是建立一套虚拟地址映射的关键。转换表分 2 部分,表索引和表项。表索引对应虚拟地址,表项对应物理地址。一对表索引和表项构成一个转换表单元,能够对一个内存块进行虚拟地址转换。(映射表基本规定中,规定了内存映射和管理是以块为单位的,至于块有多大,要看你的 MMU 的支持和你自己的选择。在 ARM 中支持 3 种块大小,细表 1KB、粗表 4KB、段 1MB)。真正的转换表就是由若干个转换表单元构成的,每个单元负责1个内存块,总体的转换表负责整个内存空间(0 - 4G)的映射。

在这里插入图片描述


(3) 整个建立虚拟地址映射的主要工作就是建立这张转换表。

(4) 转换表是放置在内存中的,放置时要求起始地址在内存中要 xx 位对齐。转换表不需要软件去干涉使用,而是将基地址 TTB 设置到 cp15 的 c2 寄存器中,然后 MMU 工作时会自动去查转换表。


3、使能 MMU 单元(cp15 的 c1 寄存器)

在这里插入图片描述

(1) cp15 的 c1 寄存器的 bit 0 控制 MMU 的开关。只要将这一个 bit 置 1 ,即可开启 MMU 。开启 MMU 之后,上层软件层的地址就必须经过TT的转换才能发给下层物理层去执行。


4、找到映射表待分析

(1) 通过符号查找,确定转换表在 lowlevel_init.S 文件的 593 行。
在这里插入图片描述

在这里插入图片描述


五、start.S 解析10

在这里插入图片描述

宏观上理解转换表:整个转换表可以看作是一个 int 类型的数组,数组中的一个元素就是一个表索引和表项的单元。数组中的元素值就是表项,这个元素的数组下标就是表索引。

在这里插入图片描述

ARM 的段式映射中,长度为 1MB,因此一个映射单元只能管 1MB 内存,那我们整个 4G 范围内需要 4G/1MB = 4096 个映射单元,也就是说这个数组的元素个数是 4096.实际上我们做的时候并没有依次单个处理这 4096 个单元,而是把 4096 个分成几部分,然后每部分用 for 循环做相同的处理。


1、宏 FL_SECTION_ENTRY

在这里插入图片描述

.macro FL_SECTION_ENTRY base,ap,d,c,b
	.word (\base << 20) | (\ap << 10) | \
	      (\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
.endm
  • 1
  • 2
  • 3
  • 4

我们可以看到,base << 20,意思就是,左移 20位,就是以 M (k,M,G…)字节为单位进行划分;

因此 base 如果是 5,FL_SECTION_ENTRY 5,ap,d,c,b 就映射起始地址为 5M(到 6M,因为每一个表项的大小是 1 M)的地址空间,后面的 ap,d,c,b 是映射的这段地址区域的访问控制信息。


2.

在这里插入图片描述

对于上面这段代码的理解:

.set __base,0,把 __base 设置为 0。
.rept ... .endr 之间的语句,是一个循环;每次 __base 递增 1,循环次数为 0x100(256)。

base 的取值映射表的“表项”(物理地址)映射表的“表索引”(虚拟地址,以 M 字节划分)
00x0000_0000 (0 << 20)0x0 (M)
10x0010_0000 (1 << 20)0x1 (M)
20x0020_0000 (2 << 20)0x2 (M)
30x0030_0000 (3 << 20)0x3 (M)
… (M)
0xFF0x0FF0_0000 (0xFF << 20)0xFF (M)

因此,这段循环就映射了一段物理地址空间: 0x0 ~ (0x1000,0000 - 1)。因此,这段循环映射的物理地址范围是:0 ~ 256M - 1,总共拥有的表索引是 0 ~ 255。

通过 uboot 的注释可以看到,这段地址区域,确实是给 iRAM 空间使用的。
在这里插入图片描述


3.

接着我们继续分析接下来的 uboot 代码。
在这里插入图片描述

base 的取值映射表的“表项”(物理地址)映射表的“表索引”(虚拟地址,以 M 字节划分)
null00x100 (M)
null00x101 (M)
null00x102 (M)
null00x103 (M)
… (M)
null00x1FF (M)

虚拟地址范围 0x1000_0000 ~ 0x1FFF_FFFF,将其映射到物理地址 0 ,即无效地址。

如下图可以看到,物理地址 0x1000_0000 ~ 0x1FFF_FFFF 确实是保留区,不应该被访问。

所以将虚拟地址范围 0x1000_0000 ~ 0x1FFF_FFFF映射为无效地址,是符合逻辑的。

在这里插入图片描述


4.

在这里插入图片描述

base 的取值映射表的“表项”(物理地址)映射表的“表索引”(虚拟地址,以 M 字节划分)
0x2000x2000_00000x200 (M)
0x2010x2010_00000x201 (M)
0x2020x2020_00000x202 (M)
0x2030x2030_00000x203 (M)
… (M)
0x5FF0x5FF0_00000x5FF (M)

虚拟地址范围 0x2000_0000 ~ 0x5FFF_FFFF,将其映射到物理地址 0x2000_0000 ~ 0x5FFF_FFFF 。

如下图可以看到,物理地址 0x2000_0000 ~ 0x5FFF_FFFF 是 DDR 内存区域。

在这里插入图片描述

关于 DRAM 内存空间的区域,uboot 指定的地址详情如下图。详情可以参考博文:链接地址。
在这里插入图片描述


5.

在这里插入图片描述

base 的取值映射表的“表项”(物理地址)映射表的“表索引”(虚拟地址,以 M 字节划分)
null00x600 (M)
null00x601 (M)
null00x602 (M)
null00x603 (M)
… (M)
null00x7FF (M)

虚拟地址范围 0x6000_0000 ~ 0x7FFF_FFFF,将其映射到物理地址 0 ,即无效地址。

如下图可以看到,物理地址 0x6000_0000 ~ 0x7FFF_FFFF 确实是 uboot 中未设置的 DDR 区域,不应该被访问。

所以将虚拟地址范围 0x6000_0000 ~ 0x7FFF_FFFF 映射为无效地址,是符合逻辑的。
在这里插入图片描述


6.

在这里插入图片描述

base 的取值映射表的“表项”(物理地址)映射表的“表索引”(虚拟地址,以 M 字节划分)
0x8000x8000_00000x800 (M)
0x8010x8010_00000x801 (M)
0x8020x8020_00000x802 (M)
0x8030x8030_00000x803 (M)
… (M)
0xaFF0xaFF0_00000xaFF (M)

虚拟地址范围 0x8000_0000 ~ 0xaFFF_FFFF,将其映射到物理地址 0x8000_0000 ~ 0xaFFF_FFFF 。

如下图可以看到,物理地址 0x8000_0000 ~ 0xaFFF_FFFF 是 SROMC_BANK 区域。

在这里插入图片描述


7.

在这里插入图片描述

base 的取值映射表的“表项”(物理地址)映射表的“表索引”(虚拟地址,以 M 字节划分)
0xb000xb000_00000xb00 (M)
0xb010xb010_00000xb01 (M)
0xb020xb020_00000xb02 (M)
0xb030xb030_00000xb03 (M)
… (M)
0xbFF0xbFF0_00000xbFF (M)

虚拟地址范围 0xb000_0000 ~ 0xbFFF_FFFF,将其映射到物理地址 0xb000_0000 ~ 0xbFFF_FFFF 。

如下图可以看到,物理地址 0xb000_0000 ~ 0xbFFF_FFFF 是 ONENAND/NAND 区域。

在这里插入图片描述


8.

在这里插入图片描述

base 的取值映射表的“表项”(物理地址)映射表的“表索引”(虚拟地址,以 M 字节划分)
0x3000x3000_00000xc00 (M)
0x3010x3010_00000xc01 (M)
0x3020x3020_00000xc02 (M)
0x3030x3030_00000xc03 (M)
… (M)
0x3FF0x3FF0_00000xcFF (M)

虚拟地址范围 0xc000_0000 ~ 0xcFFF_FFFF,将其映射到物理地址 0x3000_0000 ~ 0x3FFF_FFFF 。

如下图可以看到,物理地址 0x3000_0000 ~ 0x3FFF_FFFF 是 uboot 设置的 DMC0 区域。

在这里插入图片描述

在这里插入图片描述


9.

在这里插入图片描述

base 的取值映射表的“表项”(物理地址)映射表的“表索引”(虚拟地址,以 M 字节划分)
0xd000xd000_00000xd00 (M)
0xd010xd010_00000xd01 (M)
0xd020xd020_00000xd02 (M)
0xd030xd030_00000xd03 (M)
… (M)
0xFFF0xFFF0_00000xFFF (M)

虚拟地址范围 0xd000_0000 ~ 0xFFFF_FFFF,将其映射到物理地址 0xd000_0000 ~ 0xFFFF_FFFF 。

如下图可以看到,物理地址 0xd000_0000 ~ 0xFFFF_FFFF 代表的区域。
在这里插入图片描述


总结

VA					PA					length
0-10000000			0-100'			    256MB
10000000-20000000	0					256MB
20000000-60000000	20000000-60000000	1GB		512-1.5G
60000000-80000000	0					512MB	1.5G-2G
80000000-b0000000	80000000-b0000000	768MB	2G-2.75G
b0000000-c0000000	b0000000-c0000000	256MB	2.75G-3G
c0000000-d0000000	30000000-40000000	256MB	3G-3.25G
d-完				d-完				768MB	3.25G-4G
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

DRAM 的有效范围:
DMC0: 0x30000000 ~ 0x3FFFFFFF
DMC1: 0x40000000 ~ 0x4FFFFFFF

结论:虚拟地址映射只是把虚拟地址的 c0000000 开头的 256MB ,映射到了 DMC0 的 30000000 开头的 256MB 物理内存上去了。其他的虚拟地址空间根本没动,还是原样映射的。

思考:为什么配置时将链接地址设置为 c3e00000,因为这个地址将来会被映射到33e00000 这个物理地址。


六、start.S 解析11

1、再次设置栈

在这里插入图片描述

(1) 第三次设置栈。这次设置栈还是在 DDR 中,之前虽然已经在 DDR 中设置过一次栈了,但是本次设置栈的目的,是将栈放在比较合适(安全,紧凑而不浪费内存)的地方。

(2) 我们实际将栈设置在 uboot 起始地址上方 2MB 处,这样安全的栈空间是:2MB-uboot大小-0x1000= 1.8MB 左右。这个空间既没有太浪费内存,又足够安全。


2、清理bss

在这里插入图片描述

(1) 清理 bss 段代码和裸机中讲的一样。注意表示 bss 段的开头和结尾地址的符号是从链接脚本 u-boot.lds 得来的。


3、ldr pc, _start_armboot

在这里插入图片描述

(1) start_armbootuboot/lib_arm/board.c 中,这是一个 C 语言实现的函数。这个函数就是 uboot 的第二阶段

这句代码的作用,就是将 uboot 第二阶段执行的函数的地址传给 pc。实际上就是使用一个远跳转,直接跳转到 DDR 中的第二阶段开始地址处

(2) 远跳转的含义就是,这句语句加载的地址和当前运行地址无关,而和链接地址有关。因此这个远跳转可以实现,从 SRAM 中的第一阶段跳转到 DDR 中的第二阶段

(3) 这里这个远跳转 ldr pc, _start_armboot,就是 uboot 的第一阶段和第二阶段的分界线


4、总结:uboot的第一阶段做了哪些工作

(1) 构建异常向量表;
(2) 设置 CPU 为 SVC 模式;
(3) 关看门狗;
(4) 开发板供电置锁;
(5) 时钟初始化;
(6) DDR 初始化;
(7) 串口初始化并打印"OK";
(8) uboot 工程的重定位;
(9) 建立映射表并开启 MMU;
(10) 跳转到第二阶段(BL2);


三次设置 sp 栈指针

  • 第一次:因为 DDR 尚未初始化,因此程序执行都是在 SRAM 中,所以在 SRAM 中分配了一部分内存作为栈。

  • 第二次:因为 DDR 已经被初始化了,因此要把栈挪移到 DDR 中,所以要重新设置栈(start.S 297-299 行);这里实际设置的栈的地址是 33E00000,刚好在 uboot 的代码段的下面紧挨着。

    为什么要再次设置栈?因为 DDR 已经初始化了,已经有大片内存可以用了,没必要再把栈放在 SRAM 中可怜兮兮的了;原来 SRAM 中内存大小空间有限,栈放在那里要注意不能使用过多的栈,否则栈会溢出,我们及时将栈迁移到 DDR 中,也是为了尽可能避免栈使用时候的小心翼翼。

  • 第三次:这次设置栈还是在 DDR 中,之前虽然已经在 DDR 中设置过一次栈了,但是本次设置栈的目的,是将栈放在比较合适(安全,紧凑而不浪费内存)的地方。

    我们实际 将栈设置在 uboot 起始地址上方 2MB 处,这样安全的栈空间是:2MB-uboot大小-0x1000= 1.8MB 左右。这个空间既没有太浪费内存,又足够安全。


源自朱有鹏老师.

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/129998
推荐阅读
相关标签
  

闽ICP备14008679号