1. 下面代码是系统启动后U-boot上电后运行的第一段代码,他是
什么意思?
.globl _start
_start:
b
reset
ldr
pc, _undefined_instruction
ldr
pc, _software_interrupt
ldr
pc, _prefetch_abort
ldr
pc, _data_abort
ldr
pc, _not_used
ldr
pc, _irq
ldr
pc, _fiq
_undefined_instruction :
.word undefined_instruction
_software_interrupt:
.word software_interrupt
_prefetch_abort:
.word prefetch_abort
_data_abort:
.word data_abort
_not_used:
.word not_u
sed
_irq:
.word irq
_fiq:
.word fiq
.balignl 16,0xdeadbeef
他们是系统定义的异常,一 上电程序跳转到re set异常处执行相应 的 汇编指令 , 下面定义出的都是不同的异 常, 比如软件发生软中断时 ,CPU就会去执行软中断的指令 ,这些异常中断在CUP中地址是从0开始 ,每 个异常占4个字节。
reset:
mrs
r0,cpsr
bic
r0,r0,#0x1f
orr
r0,r0,#0xd3
msr
cpsr,r0
操作系统先注册一个总的中断,然后去查是由哪个中断源产生的中断 ,再去查用户注册的中断表,查出来后就去执行用户定义的用户中断处理函数。
ldr
pc, _undefined_instruction表示把_undefined_instruction存放的数值存放到pc指针上,_undefined_instruction:
.word undefined_instruction表示未定义的这个异常是由.word来定义的,它表示定义一个字,一个32位的数,.word后面的数表示把该标识的编译地址写入当前地址,标识是不占用任何指令的。把标识存放的数值copy到指针pc上面,那么标识上存放的值是什么?是由.word undefined_instruction来指定的,pc就代表你运行代码的地址,她就实现了CPU要做一次跳转时的工作。
什么是编译地址?什么是运行地址?
32位的处理器,它的每一条指令是4个字 节 ,以4个字节存 储顺序,进行顺序执行,CPU是顺序执行的 ,只要没发生什么跳转,它会顺序进行执行,编译器会对 每一条指令分配一个编译地址 ,这是编译器分配的,在编译过程中分配的地址,我们称之为编译地址。
运行地址是指,程序指令真正运 行的地址, 是由用户指定的 ,用户将运行地址烧录到哪里 ,哪里就是运行的地址。比如有一个指令的编译地址是0 x5,实 际运行的地址是0x200 ,如果用户将指令烧到0x200上 ,那么这条指令的运行地址就是0x200,当编译地址和 运行地址不同的时候会出现什么结果 ?结果是不能跳转,编译后会产生跳转地址,如果实际地址和编译后产生的地址不相等, 那么就不能跳转。C语言编译地址都希望把编译地址和 实际运行地址放在一起的,但是汇编代码因为不需要 做C语言到汇编的转换, 可以认为的去 写地址, 所以直接写的就是他的运行地址,这就是为什么任何bootload er刚开始会 有一段汇编代码,因为起始代码 编译地址和 实际地址不相等,这段代码和汇编无关,跳 转用的运行地址。 编译地址和运行地址如何来算呢? 假如有两个编译地址a=0x10,b=0x7,b的运行 地址是0x300,那么 a的运行地址就是b的运行 地址加上两者编译地址 的差值,a-b=0x10-0x7=0x3,a的运行地址就是0x300+0x3= 0x303。
假设uboot上两条指令的编译地址为a=0x3 3000007和b=0x330 00001,这两 条指令都落在bank6上,现在要计算出他们对应的运行地址,要找出运行地址的始地址,这个是由用户 烧录进去的,假设运行地址的首地址 是0x0,则a的运行地址
为0x7,b为0x1,就是这样算出来的。
为什么要分配编译地址?这样做有什么好处,有什么作用?
比如在函数a中定义了函数b ,当执 行到函数b时要进行指令跳转 ,要跳转到b函数所对应的起始地址上去 ,编译时,编译器给每条指令都分配了编译地址 ,如果编译器已经给分配 了地址 就可以直接进行跳转,查 找b函数跳转指令所对应的表,进行直接跳转,因为有个编译地址和指令对应的一个表,如果没有分配,编译器就 查找不到这个跳转地址,要进行计算,非常麻烦。
什么是相对地址?
以NOR Flash为例,NOR Falsh是映射到bank0上面,SDRAM是映射到bank6上面,uboot和内核最终 是在SDRAM上面运行,最开始我们是从Nor Flash的零地址 开始 往 后烧录,ubo ot中至少有一段代码 编 译地址和 运行地址是不一样的,编译uboot或内核时,都会将编译地址放 入到SDRAM中, 他们 最终都 会在SDRAM中执行,刚开始uboot 在 Nor Flash中运行,运行地址是一个低端地址, 是bank0中的一个地址, 但编译地址是bank6中的地址,这样就会导致 绝对跳转指令执行的失败,所以就引出了相对地址的概念。那么什么是相对地址 呢?至少在 bank0中uboot这段代码要知道不能用b+编译地址这样的方法去跳转指令,因为 这段代码的编译地址和运行地址不一样,那如何去做呢?要去计算这个指令运行的真实地址,计算出来后再做跳转,应 该是b+运行 地址, 不能 出现b+编译地址,而是b+运行地址,而运行地址是算出来的。
_TEXT_BASE:
.word
TEXT_BASE
//0x33F80000,在board/config.mk中
这段话表示,用户告诉编译器编译地址的起始地址
Uboot启动流程
1.
设置CPU的启动模式
reset:
//设置CPU进入管理模式 即设置 相应的CPSR程序状态字
mrs
r0,cpsr
bic
r0,r0,#0x1f
orr
r0,r0,#0xd3
msr
cpsr,r0
2.
关闭看门狗,关闭中断,所谓
的喂狗是每隔一段时
间给某个寄存器置位而已
,在实际中会专门启动一个线程或进程会专门喂狗,当上层软件出现故障时就会停止喂狗,停止喂狗之后,cpu会自动复位,一般都在外部专门有一个看门狗,做一个外部的电路,不在cpu内部使用看门狗,cpu内部的看门狗是复位的cpu,当开发板很复杂时,有好几个cpu时,就不能完全让板子复位,但我们通常都让整个板子复位。
看门狗每隔短时间就会喂狗,问题是在两次喂狗之间的时间间隔内,运行
的代
码的时间是否够用,两次
喂狗之间的代码是否在
两次喂狗的时间延迟之内,如果在延迟之外的话,代码还没改完就又进行喂狗,代码永远也改不
完。
//关闭看门狗的实际代码
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr
r0, =pWTCON //将pwtcon寄存器地址赋给R0
mov
r1, #0x0 //r1的内容为0
str
r1, [r0]
//将R1的内容送到Ro寄存器中去
3. 屏蔽所有中断,为什么要关中断?中 断处理 中ldr p c是将代码的 编译地址放在了指针上 ,而这段时间还没有搬移代码,所以编译地址上面没有这个代码,如果进行跳转就会跳转到空指针上面
mov
r1, #0xffffffff
//寄存器中的值全为111111111111111111111111
11111111
ldr
r0, =INTMSK
//将管理中断的寄存器地址赋给ro
str
r1, [r0]
//将
全1的值赋给ro地址中的内容
#if defined(CONFIG_S3C2410)
ldr
r1, =0x3ff
ldr
r0, =INTSUBMSK
str
r1, [r0]
#endif
3.
设置时钟分频,为什么要设置时钟?起始可以不设,系统能不能跑起来和频率没有任何关系,频率的设置是要让外围的设备能承受所设置的频率,如果频率过高则会导致cpu操作外围设备失败
//设置CPU的频率
ldr
r0, =CLKDIVN
mov
r1, #3
str
r1, [r0]
4. 做bank的设置
cpu_init_crit:
mov
r0, #0
mcr
p15, 0, r0, c7, c7, 0
mcr
p15, 0, r0, c8, c7, 0
//协处理器
//禁止MMU
mrc
p15, 0, r0, c1, c0, 0
bic
r0, r0, #0x00002300
@ clear bits 13, 9:8 (--V- --RS)
bic
r0, r0, #0x00000087
@ clear bits 7, 2:0 (B--- -CAM)
orr
r0, r0, #0x00000002
@ set bit 2 (A) Align
orr
r0, r0, #0x00001000
@ set bit 12 (I) I-Cache
mcr
p15, 0, r0, c1, c0, 0
//关闭
为什么要关闭catch和MMU呢?catch和MMU是做什么用的?
Catch是cpu内部的一个2级缓存,她的作用是将常用的数据和指令放在cpu内部,MMU是用来做虚实地址转换用的,我们的目的是设置控制的寄存器,寄存器都是实地址,如果既要开启MMU又要做虚实地址转换的话,中间还多一步,
先要把实地址转换成虚地址,然后再做设置,但对uboot而言就是起到一个简单的初始化的作用和引导操作系统,如果开启MMU的话,很麻烦,也没必要,所以关闭MMU.
说道catch就必须提到一个关键字Volatile,以后在设置寄存器时会经常遇到,他的本质是告诉编译器不要对我的代码进行优化,优化的过程是将常用的代码取出来放到catch中,它
没有从实际的物理地址去取,它直接从cpu的缓存中去取,但常用的代码就是为了感知一些常用变量的变化,如果正在取数据的时候发生跳变,那么就感觉不到变量的变化了,所以在这种情况下要用Volatile关键字告
诉编译器不要做优化,每次从实际的物理地址中去取指令,这就是为什么关闭catch关闭MMU。但在C
语言中是不会关闭catch和MMU的,会打开,如果编写者要感知外界变化,或变化太快,从catch中取数据会有误差,就加一个关键字Volatile。
5. bl
lowlevel_init下来初始化各个bank,把各个bank设置必须搞清楚,对以后移植复杂的uboot有很大帮助
6.设置完毕后拷贝uboot代码到4k空间 ,拷贝完毕后执行内存中的uboot代码
以上流程基本上适用于所有的bootloader,这就 是step1阶段
7. 问题:如果换一块开发板有可能改哪些东西?
首先,cpu的运行模式,如
果需要对cpu进行设置那就
设置
,管看门狗,关中断不用改,时钟有可能要改,如果能正常使用则不用改,关闭catch和MMU不用改,设置bank有可能要改。最后一步拷贝
时看地址会不会变,如果变化也要改,执行内存中代码,地址有可能要改。
8. Nor Flash和Nand Flash 本 质 区别 就在 于是否 进 行 代码拷贝,也 就 是下面代码所表 述 : 无论是Nor Flash还是Nand Flash,核心思想就是将uboot代码搬 运 到内 存 中去运 行 , 但 是 没有拷贝bss后面这段代码 , 只拷贝bss前面的代码,bss代码是放置全局变量的 。Bss段代码 是为了清零,拷 贝过去再清零 重复 操作
//uboot代码搬运到RAM中 去
#ifndef CONFIG_SKIP_RELOCATE_UB OOT
relocate:
adr
r0, _start
ldr
r1, _TEXT_BASE
cmp
r0, r1
beq
stack_setup
ldr
r2, _armboot_start
//flash中armboot_start的起始地址
ldr
r3, _bss_start
//uboot_bss的起始地址
sub
r2, r3, r2
add
r2, r0, r2
copy_loop:
ldmia
r0!, {r3-r10}
stmia
r1!, {r3-r10}
cmp
r0, r2
ble
copy_loop
#endif
.globl _start
_start:
_undefined_instruction :
_software_interrupt:
_prefetch_abort:
_data_abort:
_not_used:
_irq:
_fiq:
他们是系统定义的异常,一 上电程序跳转到re set异常处执行相应 的 汇编指令 , 下面定义出的都是不同的异 常, 比如软件发生软中断时 ,CPU就会去执行软中断的指令 ,这些异常中断在CUP中地址是从0开始 ,每 个异常占4个字节。
reset:
操作系统先注册一个总的中断,然后去查是由哪个中断源产生的中断 ,再去查用户注册的中断表,查出来后就去执行用户定义的用户中断处理函数。
ldr
什么是编译地址?什么是运行地址?
32位的处理器,它的每一条指令是4个字 节 ,以4个字节存 储顺序,进行顺序执行,CPU是顺序执行的 ,只要没发生什么跳转,它会顺序进行执行,编译器会对 每一条指令分配一个编译地址 ,这是编译器分配的,在编译过程中分配的地址,我们称之为编译地址。
运行地址是指,程序指令真正运 行的地址, 是由用户指定的 ,用户将运行地址烧录到哪里 ,哪里就是运行的地址。比如有一个指令的编译地址是0 x5,实 际运行的地址是0x200 ,如果用户将指令烧到0x200上 ,那么这条指令的运行地址就是0x200,当编译地址和 运行地址不同的时候会出现什么结果 ?结果是不能跳转,编译后会产生跳转地址,如果实际地址和编译后产生的地址不相等, 那么就不能跳转。C语言编译地址都希望把编译地址和 实际运行地址放在一起的,但是汇编代码因为不需要 做C语言到汇编的转换, 可以认为的去 写地址, 所以直接写的就是他的运行地址,这就是为什么任何bootload er刚开始会 有一段汇编代码,因为起始代码 编译地址和 实际地址不相等,这段代码和汇编无关,跳 转用的运行地址。 编译地址和运行地址如何来算呢? 假如有两个编译地址a=0x10,b=0x7,b的运行 地址是0x300,那么 a的运行地址就是b的运行 地址加上两者编译地址 的差值,a-b=0x10-0x7=0x3,a的运行地址就是0x300+0x3= 0x303。
假设uboot上两条指令的编译地址为a=0x3 3000007和b=0x330 00001,这两 条指令都落在bank6上,现在要计算出他们对应的运行地址,要找出运行地址的始地址,这个是由用户 烧录进去的,假设运行地址的首地址 是0x0,则a的运行地址
为0x7,b为0x1,就是这样算出来的。
为什么要分配编译地址?这样做有什么好处,有什么作用?
比如在函数a中定义了函数b ,当执 行到函数b时要进行指令跳转 ,要跳转到b函数所对应的起始地址上去 ,编译时,编译器给每条指令都分配了编译地址 ,如果编译器已经给分配 了地址 就可以直接进行跳转,查 找b函数跳转指令所对应的表,进行直接跳转,因为有个编译地址和指令对应的一个表,如果没有分配,编译器就 查找不到这个跳转地址,要进行计算,非常麻烦。
什么是相对地址?
以NOR Flash为例,NOR Falsh是映射到bank0上面,SDRAM是映射到bank6上面,uboot和内核最终 是在SDRAM上面运行,最开始我们是从Nor Flash的零地址 开始 往 后烧录,ubo ot中至少有一段代码 编 译地址和 运行地址是不一样的,编译uboot或内核时,都会将编译地址放 入到SDRAM中, 他们 最终都 会在SDRAM中执行,刚开始uboot 在 Nor Flash中运行,运行地址是一个低端地址, 是bank0中的一个地址, 但编译地址是bank6中的地址,这样就会导致 绝对跳转指令执行的失败,所以就引出了相对地址的概念。那么什么是相对地址 呢?至少在 bank0中uboot这段代码要知道不能用b+编译地址这样的方法去跳转指令,因为 这段代码的编译地址和运行地址不一样,那如何去做呢?要去计算这个指令运行的真实地址,计算出来后再做跳转,应 该是b+运行 地址, 不能 出现b+编译地址,而是b+运行地址,而运行地址是算出来的。
_TEXT_BASE:
这段话表示,用户告诉编译器编译地址的起始地址
Uboot启动流程
1.
reset:
//设置CPU进入管理模式 即设置 相应的CPSR程序状态字
2.
//关闭看门狗的实际代码
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
3. 屏蔽所有中断,为什么要关中断?中 断处理 中ldr p c是将代码的 编译地址放在了指针上 ,而这段时间还没有搬移代码,所以编译地址上面没有这个代码,如果进行跳转就会跳转到空指针上面
#if defined(CONFIG_S3C2410)
#endif
3.
//设置CPU的频率
4. 做bank的设置
cpu_init_crit:
//禁止MMU
为什么要关闭catch和MMU呢?catch和MMU是做什么用的?
Catch是cpu内部的一个2级缓存,她的作用是将常用的数据和指令放在cpu内部,MMU是用来做虚实地址转换用的,我们的目的是设置控制的寄存器,寄存器都是实地址,如果既要开启MMU又要做虚实地址转换的话,中间还多一步,
先要把实地址转换成虚地址,然后再做设置,但对uboot而言就是起到一个简单的初始化的作用和引导操作系统,如果开启MMU的话,很麻烦,也没必要,所以关闭MMU.
5. bl
6.设置完毕后拷贝uboot代码到4k空间 ,拷贝完毕后执行内存中的uboot代码
以上流程基本上适用于所有的bootloader,这就 是step1阶段
7. 问题:如果换一块开发板有可能改哪些东西?
8. Nor Flash和Nand Flash 本 质 区别 就在 于是否 进 行 代码拷贝,也 就 是下面代码所表 述 : 无论是Nor Flash还是Nand Flash,核心思想就是将uboot代码搬 运 到内 存 中去运 行 , 但 是 没有拷贝bss后面这段代码 , 只拷贝bss前面的代码,bss代码是放置全局变量的 。Bss段代码 是为了清零,拷 贝过去再清零 重复 操作
//uboot代码搬运到RAM中 去
#ifndef CONFIG_SKIP_RELOCATE_UB OOT
relocate:
copy_loop:
#endif