一乐电子

 找回密码
 请使用微信账号登录和注册会员

QQ登录

只需一步,快速开始

微信扫码登录

搜索
查看: 4652|回复: 3

Uboot详解

[复制链接]
发表于 2017-1-13 17:19 | 显示全部楼层 |阅读模式
大多数bootloader都分为stage1stage2两部分,u-boot也不例外。依赖于CPU体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。/ H/ C$ V1 b+ U4 m/ i& O( B8 L6 P- a
1Stage1 start.S代码结构 : }7 s' ]; u2 y& J3 K  w0 i
u-bootstage1代码通常放在start.S文件中,他用汇编语言写成,其主要代码部分如下:% h" R# Q* P+ D4 Y& o; h
1)定义入口。由于一个可执行的Image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在ROMFlash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。
+ p  s# C) b8 }8 r! T, l! E
2)设置异常向量(Exception Vector)。6 ^7 q- @/ O3 {; ]
3)设置CPU的速度、时钟频率及终端控制寄存器。
5 `2 c1 z) s$ o& \
4)初始化内存控制器。
3 e) V" \% G& V. T/ ?3 |2 c
5)将ROM中的程序复制到RAM中。7 C$ x' ]+ c6 M8 a8 O. p4 `
6)初始化堆栈。
6 d$ H" ?* V1 C6 W
7)转到RAM中执行,该工作可使用指令ldr pc来完成。( Y5 a- ~, P# P! Y, e" l
2Stage2 C语言代码部分( Z* Q* f, @" ]/ {7 c
lib_ARM/board.c中的start arm bootC语言开始的函数也是整个启动代码中C语言的主函数,同时还是整个u-bootarmboot)的主函数,该函数只要完成如下操作:
0 G2 q+ m+ P; e0 F$ l4 B* y# `
1)调用一系列的初始化函数。
3 |3 }/ P) U% J, C
2)初始化Flash设备。, n8 t7 i  [5 V# \" J6 z
3)初始化系统内存分配函数。
* g, K1 j& T6 F# {
4)如果目标系统拥有NAND设备,则初始化NAND设备。
* z) a' J' @- \; O1 T* w- C
5)如果目标系统有显示设备,则初始化该类设备。
8 J, G% Q( k) S  K" \3 ~1 N
6)初始化相关网络设备,填写IPMAC地址等。$ e2 b9 I4 b/ O) W3 Z8 A, N# k" a. _! }
7)进去命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。- T3 K& X5 K+ v
3U-Boot的启动顺序(示例,其他u-boot版本类似)4 P+ I/ g4 u0 z) Z" x# b. s- b# d+ V
cpu/arm920t/start.S
3 A' }3 a" M2 q' r
! p5 c: C/ s3 ]% }+ Q
% K. V- e4 V! e7 [3 Q* V2 F
@文件包含处理
#include <config.h>
; c% ~5 `$ E& Q
@由顶层的mkconfig生成,其中只包含了一个文件:configs/<顶层makefile中6个参数的第1个参数>.h  
' c8 Q3 T6 T! y' J3 O
#include <version.h>  & @, D0 S4 M3 m2 @7 R5 E7 s+ m* ?
#include <status_LED.h>
/*8 J4 R; P. Z: F/ \: D' [2 o! e
*************************************************************************& T$ W2 V9 q" Q% a' k1 {# L
*
8 A9 o5 y; f3 c2 [4 W2 N& K) Z
* Jump vector table as in table 3.1 in [1]
4 _0 e! W5 r0 A; @
*
* z5 A6 E1 B+ J# D: E$ U! o1 j
*************************************************************************& I$ n, K. k% M, b
*/
注:ARM微处理器支持字节(8位)、半字(16位)、字(32位)3种数据类型6 e$ i- D% a* b
@向量跳转表,每条占四个字节(一个字),地址范围为0x0000 0000~@0x0000 0020$ _: `- |% n, r" L( r+ [! u
@ARM体系结构规定在上电复位后的起始位置,必须有8条连续的跳
@转指令,通过硬件实现。他们就是异常向量表。ARM在上电复位后,@是从0x00000000开始启动的,其实如果bootloader存在,在执行
@下面第一条指令后,就无条件跳转到start_code,下面一部分并没@执行。设置异常向量表的作用是识别bootloader。以后系统每当有@异常出现,则CPU会根据异常号,从内存的0x00000000处开始查表@做相应的处理
/******************************************************
;当一个异常出现以后,ARM会自动执行以下几个步骤:; h0 L, J' y. b. M" T
;1.把下一条指令的地址放到连接寄存器LR(通常是R14).---保存位置4 N4 N; a* C9 ^( `& {
;2.将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中---保存CPSR  U4 V& v. [9 E+ b: f6 c
;3.根据异常类型,强制设置CPSR的运行模式位
8 c7 a$ [2 J8 R5 ^' o
;4.强制PC(程序计数器)从相关异常向量地址取出下一条指令执行,从而跳转到相应的异常处理程序中
- f% O( u! i% e$ z# `# C) G. L! H
*********************************************************/
.globl _start  /*系统复位位置,整个程序入口*/& M) T4 h! ]/ {. W' x, k
@_start是GNU汇编器的默认入口标签,.globl将_start声明为外部程序可访问的标签,.globl是GNU汇编的保留关键字,前面加点是GNU汇编的语法  }8 D* ~3 a8 q7 H5 l# L
_start: b       start_code   @0x00
$ d/ p* z9 c0 E! L% G4 I" @
@ARM上电后执行的第一条指令,也即复位向量,跳转到start_code
@reset用b,就是因为reset在MMU建立前后都有可能发生- F: C7 Z6 z/ X8 M- C9 P% c
@其他的异常只有在MMU建立之后才会发生
2 V. ]( k: ~2 U8 N! c! d
ldr pc, _undefined_instruction /*未定义指令异常,0x04*/; a" x# a9 ~9 _( K4 H6 z
   ldr pc, _software_interrupt   /*软中断异常,0x08*/5 X* O* v- u  S0 y
   ldr pc, _prefetch_abort    /*内存操作异常,0x0c*/5 e& _/ D( y/ ^  F) V% w: M
   ldr pc, _data_abort     /*数据异常,0x10*/8 }& @+ L6 i% f+ X% ~9 {
   ldr pc, _not_used     /*未适用,0x14*/* \+ s* N) l" e& N2 b- t9 F
   ldr pc, _irq      /*慢速中断异常,0x18*/' [; I  r) \. @8 f- v  K, s
   ldr pc, _fiq      /*快速中断异常,0x1c*/
@对于ARM数据从内存到CPU之间的移动只能通过L/S指令,如:ldr r0,0x12345678为把0x12345678内存中的数据写到r0中,还有一个就是ldr伪指令,如:ldr r0,=0x12345678为把0x12345678地址写到r0中,mov只能完成寄存器间数据的移动,而且立即数长度限制在8位
_undefined_instruction: .word undefined_instruction
; |. ~1 K7 y" j! E
_software_interrupt: .word software_interrupt
7 U" A" i& K% ?0 Q6 ~* j4 ^3 Z
_prefetch_abort: .word prefetch_abort2 P' @) K1 k7 l) ~& {
_data_abort:  .word data_abort
8 A$ f3 B: A2 L' r
_not_used:  .word not_used0 G- V  B+ W0 t3 e, s
_irq:   .word irq5 H8 y, t% _8 b; M% w$ A3 f8 ~
_fiq:   .word fiq# K0 J  E. u0 Z+ t
@.word为GNU ARM汇编特有的伪操作,为分配一段字内存单元(分配的单元为字对齐的),可以使用.word把标志符作为常量使用。如_fiq:.word fiq即把fiq存入内存变量_fiq中,也即是把fiq放到地址_fiq中。
.balignl 16,0xdeadbeef
6 ~! Z: L; b9 x. e5 Z" _
@.balignl是.balign的变体
@ .align伪操作用于表示对齐方式:通过添加填充字节使当前位置
@满足一定的对齐方式。.balign的作用同.align; o, A& [9 n& f/ ~7 A
@ .align {alignment} {,fill} {,max}
8 D, {5 q( d& i3 O
@  其中:alignment用于指定对齐方式,可能的取值为2的次
@幂,缺省为4fill是填充内容,缺省用0填充。max是填充字节@数最大值,如果填充字节数超过max,  就不进行对齐,例如:
' A8 J8 R2 C* z! p* r
@  .align 4  /* 指定对齐方式为字对齐 */
【参考好野人的窝,于关u-boot中的.balignl 16,0xdeadbeef的理解http://haoyeren.blog.sohu.com/84511571.html
/*
9 _5 i. n* [5 B+ l2 O, T" U
*************************************************************************9 b$ P( n% T+ x% h
*: D# a8 U4 s5 h4 w
* Startup Code (called from the ARM reset exception vector)& m% n5 }0 z/ Y% c
*
) F; p' d1 d$ k" T- u# N/ f; o
* do important init only if we don't start from memory!3 f% ^5 G" G. Y" c9 c) @
* relocate armboot to ram) @/ t4 F4 o  W+ C6 d2 [: m
* setup stack4 Y* _8 X9 R. O- i
* jump to second stage
9 M1 j$ x! Z$ B& X  J, E- t& s
*
5 {9 c1 M0 v( W$ x0 W1 q% t2 |
*************************************************************************8 R+ F7 u* k) G$ U

1 }& p- w* w  J7 Z" L2 j
2 e# \4 M' ?3 @! u
@保存变量的数据区,保存一些全局变量,用于BOOT程序从FLASH拷贝@到RAM,或者其它的使用。
, l; s+ g7 @5 J# M5 a" l  l
@还有一些变量的长度是通过连接脚本里得到,实际上由编译器算出
@来的
/ Y0 d5 S) f9 [% ^2 d
7 x9 D; ~+ {/ r1 X  B4 Z8 N2 _( k# }; B
_TEXT_BASE:
@因为Linux开始地址是0x30000000,我这里是64M SDRAM,所以@TEXT_BASE = 0x33F80000 ???( R! i, i6 J1 D. |7 ]5 L4 }8 w
.word TEXT_BASE /*uboot映像在SDRAM中的重定位地址*/; O0 m, r3 b9 }3 z4 T+ R$ a
@TEXT_BASE在开发板相关的目录中的config.mk文档中定义, 他定
@义了代码在运行时所在的地址, 那么_TEXT_BASE中保存了这个地
@址(这个TEXT_BASE怎么来的还不清楚)
.globl _armboot_start  r7 u! c6 l, p# T0 w/ U5 w
_armboot_start:
, H+ ^; N) p% U7 H
.word _start9 S6 j' P. _* i2 @$ E/ B
@用_start来初始化_armboot_start。(为什么要这么定义一下还不明白)
/*
: c% O8 _1 W# E8 l1 m
* These are defined in the board-specific linker script.% ]9 |( y3 a# C* y
*/, F7 k9 ^: s* D0 J# [. j: m( S
@下面这些是定义在开发板目录链接脚本中的
.globl _bss_start   
  x( p% I( R9 q! i4 l9 c' N
_bss_start:: Q+ S& w# `3 z+ j
.word __bss_start, w( t1 i: ]( G$ `
@__bss_start定义在和开发板相关的u-boot.lds中,_bss_start保存的是__bss_start标号所在的地址。
.globl _bss_end' O( ^$ f. T: ?, `
_bss_end:
' O/ |2 a' U4 C: Z8 _
.word _end
" k3 J, q* ]% m, q6 d9 t+ Q! X9 u
@同上,这样赋值是因为代码所在地址非编译时的地址,直接取得该标号对应地址。
@中断的堆栈设置
#ifdef CONFIG_USE_IRQ2 D  }  n6 h0 Z+ M/ J
/* IRQ stack memory (calculated at run-time) */
" p# t% l* q) N
.globl IRQ_STACK_START
. l0 |+ s, c! H) H8 e+ k. F* T2 h
IRQ_STACK_START:! R, T& @& O4 V0 R4 b5 `, _- |" z
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
9 t# x. S+ M9 n6 {: I! E4 o
.globl FIQ_STACK_START
. D& x# G$ B5 @8 X6 {0 Z
FIQ_STACK_START:
) }6 J# g* P' M5 P! t0 V9 v7 G7 O
.word 0x0badc0de" R1 a& e7 j6 a2 {* R
#endif

: B2 |3 C8 j" E1 ?- E* J2 G
/*
: b/ l4 U* {) |
* the actual start code
& `; G8 M% p* i  P# X, ~
*/
1 `5 D7 P* h  N: J3 {; K6 k
@复位后执行程序
; Y0 K3 n2 X' Z1 Z8 c
@真正的初始化从这里开始了。其实在CPU一上电以后就是跳到这里执行的. F/ q% S' I5 v+ Z- |. x$ w+ x
reset:2 [! N; y/ l: n' @( N$ x# Y
/*, h# z/ w( ?3 ~  o. U; U
  * set the cpu to SVC32 mode
" g$ P. r  ]" F* O! r9 a
  *// a$ O  |0 m# i3 u$ o
@更改处理器模式为管理模式& m" `2 b8 g+ N" A6 l; d1 N
@对状态寄存器的修改要按照:读出-修改-写回的顺序来执行( Z+ d: i5 x% ^# S* V+ D2 z
@( j' ?0 g9 k7 v9 |
    31 30 29 28 ---   7   6   -   4    3   2   1   0
7 u% h% a4 b" L8 K$ D
    N  Z  C  V        I   F       M4  M3  M2 M1 M0: x4 T4 K( I3 q- b0 K5 i- E! Z
                                   0   0   0  0   0     User26 模式  @7 e! i* @6 o! E
                                   0   0   0  0   1     FIQ26 模式0 p( Q3 S% q3 _, E6 L! X( V9 S
                                   0   0   0  1   0     IRQ26 模式6 N, b& z1 L' P0 x3 ~1 K
                                   0   0   0  1   1     SVC26 模式' r. q# u! _* l& n  M7 f2 ]
                                   1   0   0  0   0     User 模式
) T4 d: L5 F. ^' U
                                   1   0   0  0   1     FIQ 模式
: m" D- b+ j+ s: b
                                   1   0   0  1   0     IRQ 模式- |- W) J* D7 ?$ _' M" B) Q
                                   1   0   0  1   1     SVC 模式
' @6 W5 g+ l" c; w
                                   1   0   1  1   1     ABT 模式
/ q9 Z0 L& E4 x+ |5 b! E/ X
                                   1   1   0  1   1     UND 模式
5 |1 a8 {/ ?% X- ?% S; S, C
                                   1   1   1  1   1     SYS 模式
mrs r0,cpsr) S" V  s# ]# _/ c! j6 H0 E
@将cpsr的值读到r0中
/ ]2 D. {9 a4 R2 i. }1 J) K
bic r0,r0,#0x1f8 n0 {& Z) m; q  v# m
@清除M0~M4
7 |- m/ }5 U' J/ R# H' E  C, W5 _, {
orr r0,r0,#0xd3
% t1 |( G' S/ L
@禁止IRQ,FIQ中断,并将处理器置于管理模式
: w- n' t, D8 y" A: {. E* v
msr cpsr,r0
@以下是点灯了,这里应该会牵涉到硬件设置,移植的时候应该可以不要% g) E+ s, i; h
bl coloured_LED_init. ]# h+ r* V2 T) @7 @8 G
bl red_LED_on
@针对AT91RM9200进行特殊处理' z) C1 `5 h& f3 K& Z
#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK). Z. o! P' C( W7 Z  z9 B8 Z
/*+ `* C- f: c9 `4 K. ?; }
  * relocate exception table' |7 X, ~+ F) r& Y4 y
  */
" g. \0 \' {. X9 n% X6 T
ldr r0, =_start& R. |9 |$ `7 b0 R" {
ldr r1, =0x0. L4 J( s3 u! p3 ~6 h
mov r2, #16( U4 e- X& M& ?) K, L) `# d
copyex:
/ {! W$ W* H2 C0 n6 A) l  M
subs r2, r2, #1
/ d9 m& P, z/ H3 R5 J
@sub带上了s用来更改进位标志,对于sub来说,若发生借位则C标志置0,没有则为1,这跟adds指令相反!要注意。) S1 o* w8 m& e3 n
ldr r3, [r0], #48 t5 W$ Q0 P2 V4 M
str r3, [r1], #4
: h4 K, A+ B7 P" R# G' s
bne copyex1 n: _" Y$ S! ?' B
#endif
@针对S3C2400和S3C2410进行特殊处理
5 o# m. o) @! z3 l
@CONFIG_S3C2400、CONFIG_S3C2410等定义在include/configs/下不同开发板的头文件中
0 @. A0 [6 L( I# L
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)3 h: _  W& y. ?
/* turn off the watchdog */
@关闭看门狗定时器的自动复位功能并屏蔽所有中断,上电后看门狗为开,中断为关+ s9 W, w( }3 T1 |6 w, M
# if defined(CONFIG_S3C2400)
4 J: O& p2 w3 E
#  define pWTCON  0x153000004 v, n% |. g" m- n6 ?1 p8 ^
#  define INTMSK  0x14400008 /* Interupt-Controller base addresses */$ S  G4 E4 j  k
#  define CLKDIVN 0x14800014 /* clock divisor register */; L' n6 A% G, I  W
#else @s3c2410的配置
, c& t( m) U2 B& l# X+ q' b% l
#  define pWTCON  0x53000000  
8 c7 Z! v# e3 r
@pWTCON定义为看门狗控制寄存器的地址(s3c2410和s3c2440相同), v: S7 [$ U' f/ a* \7 @
#  define INTMSK  0x4A000008 /* Interupt-Controller base addresses */
6 q' w" y- K0 m1 O0 C5 }; T' T
@INTMSK定义为主中断屏蔽寄存器的地址(s3c2410和s3c2440相同), ?1 D/ I/ N) ^4 w7 g  F
#  define INTSUBMSK  0x4A00001C) y# `, @. l9 J& g
@INTSUBMSK定义为副中断屏蔽寄存器的地址(s3c2410和s3c2440相同)
. X" e- v* u; U7 k* g: c+ f& S
#  define CLKDIVN  0x4C000014 /* clock divisor register */& S9 E3 Z5 N) c7 V7 ~* b3 T* v
@CLKDIVN定义为时钟分频控制寄存器的地址(s3c2410和s3c2440相同)2 S* W7 j  e, q+ T. w* R! U  g  t
# endif6 v7 y/ v- [. g0 o7 k8 Y2 i
@至此寄存器地址设置完毕
ldr     r0, =pWTCON
  z0 @0 {# F0 p5 r' p8 f+ R
mov     r1, #0x0
1 p+ E) B9 I7 T# a2 V9 |! K- }) `7 k
str     r1, [r0]& D+ {6 f! A, K5 o+ E( X+ Z$ Q: ~2 D
@对于S3C2440和S3C2410的WTCON寄存器的[0]控制允许或禁止看门狗定时器的复位输出功能,设置为“0”禁止复位功能。
/*
* g4 X/ j9 V; e( q& Z! }; o% G$ W0 F. G
  * mask all IRQs by setting all bits in the INTMR - default; @# m2 i$ B% q4 l
  */
  A9 y4 ?; T0 U! q; U
mov r1, #0xffffffff$ A+ y, m5 E+ x. F
ldr r0, =INTMSK
2 z3 H; ^6 h# E0 F6 z: E
str r1, [r0]
  `0 K# _- l# U2 U" R
# if defined(CONFIG_S3C2410)# }5 j& Q, a) W( m- X
ldr r1, =0x3ff  @2410好像应该为7ff才对(不理解uboot为何是这个数字)$ C7 n  V2 A. q: |2 J+ T: v4 C
ldr r0, =INTSUBMSK# C. x* G% u1 A* i4 k1 x. n
str r1, [r0]
4 Y$ Z5 _( ~2 M7 h5 F' h9 _/ f9 {) N
# endif4 }. X& D  l4 a( @6 V* c! I
@对于S3C2410的INTMSK寄存器的32位和INTSUBMSK寄存器的低11位每一位对应一个中断,相应位置“1”为不响应相应的中断。对于S3C2440的INTSUBMSK有15位可用,所以应该为0x7fff了。
/* FCLK:HCLK:PCLK = 1:2:4 */* O4 F6 S/ N! ]9 S
/* default FCLK is 120 MHz ! */
3 f; s/ l1 q2 c( b- n
ldr r0, =CLKDIVN
" {( ~! V/ e( W: F1 L
mov r1, #32 h% d- P6 m- H$ A
str r1, [r0]
! m. N  z3 Z( P. H; z# x  n4 n
@时钟分频设置,FCLK为核心提供时钟,HCLK为AHB(ARM920T,内存@控制器,中断控制器,LCD控制器,DMA和主USB模块)提供时钟,@PCLK为APB(看门狗、IIS、I2C、PWM、MMC、ADC、UART、GPIO、@RTC、SPI)提供时钟。分频数一般选择1:4:8,所以HDIVN=2,PDIVN=1,@CLKDIVN=5,这里仅仅是配置了分频寄存器,关于MPLLCON的配置肯@定写在lowlevel_init.S中了
6 |8 \% J3 ^3 |1 k
@归纳出CLKDIVN的值跟分频的关系:
! e" {' T% i: b: d1 K
@0x0 = 1:1:1  ,  0x1 = 1:1:2 , 0x2 = 1:2:2  ,  0x3 = 1:2:4,  0x4 = 1:4:4,  0x5 = 1:4:8, 0x6 = 1:3:3,
/ o3 J6 U  u5 d& j% W% n
0x7 = 1:3:6
. }+ F! a# J8 A5 r# M- l
@S3C2440的输出时钟计算式为:Mpll=(2*m*Fin)/(p*2^s), T- g8 O, E# p5 c7 u' I& D
S3C2410的输出时钟计算式为:Mpll=(m*Fin)/(p*2^s)
4 m! [+ g% e3 M4 J, X
m=M(the value for divider M)+8;p=P(the value for divider P)+2
0 T7 T- B0 G3 y" N
M,P,S的选择根据datasheet中PLL VALUE SELECTION TABLE表格进行,
$ B' ?$ N1 y$ [$ j
  q6 x" Q- ^3 v7 _
我的开发板晶振为16.9344M,所以输出频率选为:399.65M的话M=0x6e,P=3,S=1" d' |# u: g6 J. ^  U; v2 u
@s3c2440增加了摄像头,其FCLK、HCLK、PCLK的分频数还受到CAMDIVN[9](默认为0),CAMDIVN[8](默认为0)的影响1 M; \- O" e. I% Q4 E' j
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
/*
; Y6 D2 y( `  b; J
  * we do sys-critical inits only at reboot,7 H7 t. Y) y6 n3 C: R1 [  k
  * not when booting from ram!
' k& T: L8 |* z! j! N9 M
  */' Z6 n; I& z$ @8 s2 P/ x' o- c8 j& a
@选择是否初始化CPU
, \% I& i" y# f$ E2 Y* {2 y
#ifndef CONFIG_SKIP_LOWLEVEL_INIT% Y, U' d  ?# U
bl cpu_init_crit
% f: g' P' E. A
@执行CPU初始化,BL完成跳转的同时会把后面紧跟的一条指令地址保存到连接寄存器LR(R14)中。以使子程序执行完后正常返回。
) {$ g4 B- v2 V. i3 E
#endif
@调试阶段的代码是直接在RAM中运行的,而最后需要把这些代码 @固化到Flash中,因此U-Boot需要自己从Flash转移到  }5 g+ L4 o: h7 p. z
@RAM中运行,这也是重定向的目的所在。, T! G- R3 ~  T2 Z' t
@通过adr指令得到当前代码的地址信息:如果U-boot是从RAM @开始运行,则从adr,r0,_start得到的地址信息为+ W: `4 k( }2 W6 g8 p
@r0=_start=_TEXT_BASE=TEXT_BASE=0x33F80000; @如果U-bootFlash开始运行,即从处理器对应的地址运行,
, y9 D/ `! s: d8 h* k- z
@r0=0x0000,这时将会执行copy_loop标识的那段代码了。: v; ], r( n7 x: m; u" |7 R5 l
@ _TEXT_BASE 定义在board/smdk2410/config.mk
* q1 Y2 ^* m4 Y6 A( N& u! n8 u# X- c. L! v% F
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
1 G) z* N* o. X1 |/ d
relocate:    /* relocate U-Boot to RAM     */
: I: l; Z2 j3 Q; s1 d
adr r0, _start  /* r0 <- current position of code   */
+ Q8 K5 q; K0 n& L& z" v  k
ldr r1, _TEXT_BASE  /* test if we run from flash or RAM */3 G0 g4 S' j* D7 c4 b: u& G* p5 G: ~# R
cmp     r0, r1  /* don't reloc during debug         */! n/ l- M* }) ^% |$ t1 F% E
beq     stack_setup2 J: H" q& z1 C0 z
ldr r2, _armboot_start
8 r- y% y' G0 ]: A
@_armboot_start为_start地址# _/ v5 |& [* N2 |
ldr r3, _bss_start+ w3 w# k- B  i) ~' a' w/ R
@_bss_start为数据段地址
! h# G* ~6 j/ {' ~
sub r2, r3, r2  /* r2 <- size of armboot            */
, n# Y4 ~& n: _
add r2, r0, r2  /* r2 <- source end address         */
copy_loop:
% O- K' |& m( u9 z/ R
ldmia r0!, {r3-r10}  /* copy from source address [r0]    */
@从源地址[r0]读取8个字节到寄存器,每读一个就更新一次r0地址
! L0 b; x+ Z8 P! r3 t- ]
@ldmia:r0安字节增长& w7 }% C6 g- K' E
stmia r1!, {r3-r10} /* copy to   target address [r1]    */
# r. ?$ d8 b# Z% `6 O; ?7 l* L
@LDM(STM)用于在寄存器所指的一片连续存储器和寄存器列表的寄存@器间进行数据移动,或是进行压栈和出栈操作。
$ [  X6 A  b3 @+ A: N1 |9 N
@格式为:LDM(STM){条件}{类型}基址寄存器{!},寄存器列表{^}
$ n2 G  @5 s; }* l& [
@对于类型有以下几种情况: IA 每次传送后地址加1,用于移动数
@据块
( p0 O0 V' Q( j# s- H/ A- A  r
    IB 每次传送前地址加1,用于移动数据块. V4 j8 G- N9 f& {3 i  T3 B! \
    DA 每次传送后地址减1,用于移动数据块
- Y# H& k# m  Y# M* p! S  s4 e
    DB 每次传送前地址减1,用于移动数据块! d/ ?" X! I4 r9 u
    FD 满递减堆栈,用于操作堆栈(即先移动指针再操作数据,相当于DB)0 M8 B6 h9 G; h- l9 Y
    ED 空递减堆栈,用于操作堆栈(即先操作数据再移动指针,相当于DA)$ M1 ~: _3 a- }! D+ O/ C
    FA 满递增堆栈,用于操作堆栈(即先移动指针再操作数据,相当于IB)5 u% |6 o* h! q2 M
    EA 空递增堆栈,用于操作堆栈(即先操作数据再移动指针,相当于IA); _1 D8 ^8 K# H6 W  A* y- H
(这里是不是应该要涉及到NAND或者NOR的读写?没有看出来)
cmp r0, r2   /* until source end addreee [r2]    */% r8 R1 n5 }& G4 K
ble copy_loop  i6 z% U, N! |* S( @- E1 ^
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
/* Set up the stack          */+ `( X# s) i+ b3 [
@初始化堆栈' f2 w# e, c0 d. \5 q
stack_setup:
0 |! V0 K+ X3 V( U! |- U# u
ldr r0, _TEXT_BASE  /* upper 128 KiB: relocated uboot   */
@获取分配区域起始指针,
sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area    */
@CFG_MALLOC_LEN=128*1024+CFG_ENV_SIZE=128*1024+0x1@0000=192K
sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo   */
@CFG_GBL_DATA_SIZE    128---size in bytes reserved for initial data 用来存储开发板信息
8 v0 P3 K" V1 h/ Z
#ifdef CONFIG_USE_IRQ
@这里如果需要使用IRQ, 还有给IRQ保留堆栈空间, 一般不使用.9 D, i1 o' s0 B& @! J7 I
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
! I6 m9 k7 l+ g3 t6 K  w
#endif, N" s* U3 ?1 U  c8 S+ ?
sub sp, r0, #12  /* leave 3 words for abort-stack    */
@该部分将未初始化数据段_bss_start----_bss_end中的数据 @清零
% S& `; O# Q4 ~
clear_bss:
# ^; k2 U2 h/ R& |/ K+ N
ldr r0, _bss_start  /* find start of bss segment        */
/ b& B& v& i9 `  J
ldr r1, _bss_end  /* stop here                        */
' @" o  Q  ?* L; o! D' o
mov r2, #0x00000000  /* clear                            */
clbss_l:str r2, [r0]  /* clear loop...                    */
/ x: q3 d2 c5 v' @
add r0, r0, #4+ G3 z4 F. [1 `& c: a; d
cmp r0, r16 b! ^( A" q3 V! V  g( }% E$ p. |
ble clbss_l
@跳到阶段二C语言中去
4 o0 Y1 D" _! p' j
ldr pc, _start_armboot
_start_armboot: .word start_armboot
( d1 U$ F& R! z2 @+ N8 S* }
@start_armboot在/lib_arm/中,到这里因该是第一阶段已经完成了吧,下面就要去C语言中执行第二阶段了吧
/*
/ y1 r# A0 {0 q1 U& v' X7 v
*************************************************************************/ [: t6 {2 }# w2 z
*. h  V' R2 J* d  j5 u& {& H& T, E
* CPU_init_critical registers
# x  [2 T* g9 T; j+ v
*% ]9 K4 P! |3 l( Y
* setup important registers% C7 z5 F' l9 j! q; u/ L' O4 w$ P
* setup memory timing
/ I" y* s; z2 w% y* ~& g+ m
*
$ B( e% `" W, V. H: p" y4 _
*************************************************************************
+ h7 l  h3 l: f. T4 ]
*/
2 Y, u# ?. k% u9 m( \# A1 c
@CPU初始化
@在“relocate: /* relocate U-Boot to RAM */ ”之前被调用
#ifndef CONFIG_SKIP_LOWLEVEL_INIT- Z8 y& t" c/ C
cpu_init_crit:      3 l7 ?5 Q$ B  I
/*8 {" C; v' q8 ?6 `/ l
  * flush v4 I/D caches
) a6 Z7 _' S! V# w, K2 y
  */
- t- i  k; @6 Z- p3 `/ ]) v' ^" A
@初始化CACHES& U# n3 i0 U! n) h' w( n: V- F
mov r0, #0- M: X& i8 Z  E: v
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
4 p7 t: X# V% }7 B3 b" t
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*: I4 l4 ^& p8 v- f; q6 b
  * disable MMU stuff and caches$ x; W) r/ h- @+ \
  */7 ^* b( P* J4 g% X* c; \
@关闭MMU和CACHES
+ p- w+ s1 \2 A3 a' o" _0 s. \
mrc p15, 0, r0, c1, c0, 0
! L2 Q) T5 G  d' x
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)2 E8 k% ^1 L5 Z5 H
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
' V+ U8 ?0 L; z8 l+ q
orr r0, r0, #0x00000002 @ set bit 2 (A) Align* T: F' n2 j/ [% ]! \) c6 j* c
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache$ d$ ~+ G) A  \" ^2 ]. R4 {
mcr p15, 0, r0, c1, c0, 0
$ I/ A; G% ~: k2 {9 U+ J5 n& ~: `
@对协处理器的操作还是看不懂,暂时先不管吧,有时间研究一下ARM技术手册的协处理器部分。
/*, e" J2 M) v3 ^
  * before relocating, we have to setup RAM timing
* `9 _# f+ b' H
  * because memory timing is board-dependend, you will
0 @' t1 w0 N* t9 f! f) J6 Z5 @
  * find a lowlevel_init.S in your board directory.
' P+ i( S/ M% \3 V0 u  Q' A
  */* M! l: j0 [3 n* P7 K
@初始化RAM时钟,因为内存是跟开发板密切相关的,所以这部分在/开发板目录/lowlevel_init.S中实现
, Y9 v8 w" O- y. ]
mov ip, lr
" \: d* b$ V6 @8 W, D
@保存LR,以便正常返回,注意前面是通过BL跳到cpu_init_crit来的。% O2 {/ ^  i3 k; J, F
@(ARM9有37个寄存器,ARM7有27个): C  K0 a  t3 q8 s: _. @% @/ y
37个寄存器=7个未分组寄存器(R0~R7)+ 2×(5个分组寄存器R8~R12)+6×2(R13=SP,R14=lr 分组寄存器) + 1(R15=PC) +1(CPSR) + 5(SPSR)
4 y( b" L- h; F& C2 U
用途和访问权限:
1 L& Z+ P; L6 n/ ]4 v5 b
R0~R7:USR(用户模式)、fiq(快速中断模式)、irq(中断模式)、svc(超级用法模式)、abt、und
3 x; |  n% g0 D/ I  y5 p. n
R8~R12:R8_usr~R12_usr(usr,irq,svc,abt,und)' [* R1 W5 d" M+ C0 H& `
         R8_fiq~R12_fiq(fiq)
  f: D; u% l+ x- E
R11=fp: ~- `1 F& h: a
R12=IP(从反汇编上看,fp和ip一般用于存放SP的值)
' g& s2 }& A. m0 `0 q
R13~R14:R13_usr R14_usr(每种模式都有自己的寄存器)3 b/ ?/ R  ~" i) @& G
SP ~lr :R13_fiq R14_fiq. {2 W& y, w4 x% z( v% i0 e4 k
          R13_irq R14_irq" k; y1 [; t1 {$ t" R; `
          R13_svc R14_svc  l8 M5 X( l, K- r0 B" o
          R13_abt R14_abt  `5 R  M; [0 y3 O5 o; [9 i
          R13_und R14_und2 ?# e; s" f, m. S4 S
R15(PC):都可以访问(即PC的值为当前指令的地址值加8个字节)8 l* j! }* B* o! p
R16    :((Current Program Status Register,当前程序状态寄存器))3 u& o6 u, I3 Q9 q: t2 f3 U4 R
           SPSR _fiq,SPSR_irq,SPSR_abt,SPSR_und(USR模式没有)
#if defined(CONFIG_AT91RM9200EK)
#else
  d+ V- p1 ~* V
bl lowlevel_init
@在重定向代码之前,必须初始化内存时序,因为重定向时需要将@flash中的代码复制到内存中lowlevel_init在@/board/smdk2410/lowlevel_init.S中。               
#endif
& l% V3 o0 u# l6 {. U
mov lr, ip
) J/ a. s& q0 R* H) k! F7 [  Z% l
mov pc, lr
& F: P3 Y0 n$ ]  ^( Q& @
@返回到主程序
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
/*  U+ S9 P" J. @- N
*************************************************************************, r! ]1 I1 b# Q
*
3 g3 ^4 H9 N6 s. N' O; M
* Interrupt handling: N  k+ N5 m1 j6 U- q! S( ?
*
; W! Y* _! ]' P& s* Z# ?6 \
*************************************************************************7 S6 X  c' e" ?& p5 `
*/
4 d. j3 y" X( v; }
@这段没有看明白,不过好像跟移植关系不是很大,先放一放。
# k8 j5 }% q  }" ]7 A) U
@
0 O2 M; y9 s& m2 Z
@ IRQ stack frame.! M! h2 C- @2 F8 W* h6 Z( Q
@* Q+ p$ V- Q! S) L% Z  ]
#define S_FRAME_SIZE 72
#define S_OLD_R0 68+ t% y  @  }9 }5 ^! a- w( w
#define S_PSR  645 O+ T  y9 Y! q0 Q" y7 H: V! J( F
#define S_PC  60
: R* a6 B$ O  \2 y, \7 \
#define S_LR  56
9 R9 D6 d% c, o5 i" B1 {- R) {
#define S_SP  52
#define S_IP  48  r% Q( s1 C5 T! x+ }7 L; e
#define S_FP  445 n' Q/ O) ?( P$ ?' l9 ^  H
#define S_R10  40, |+ }; z5 `1 a' ], i4 _% c' e
#define S_R9  369 T) u+ ~2 ~$ D# ^; [+ v
#define S_R8  32/ ^9 ]1 y9 q) ?: ]3 D& Z5 W' Y# }
#define S_R7  28
  r' s  {  d: ?  |2 w
#define S_R6  24- y9 D( q  }( b* u
#define S_R5  206 u8 E" W2 @* j! @+ a; b4 E( r
#define S_R4  16- K8 c* j! l+ y2 M7 H) y( Q
#define S_R3  12& S8 X6 _4 b: m4 }2 r
#define S_R2  8
0 q4 T1 h0 i8 s, E
#define S_R1  4* E5 \, ~3 ^" g$ m7 s  L; f
#define S_R0  0
#define MODE_SVC 0x13" T) D3 |$ Y3 o- p1 I( O
#define I_BIT  0x80
/*& P1 r4 }, x( s1 ~6 n: D
* use bad_save_user_regs for abort/prefetch/undef/swi ...
) C9 P# q2 L4 @8 I# y2 U
* use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
% b# d# {) E8 t) X) H
*/
.macro bad_save_user_regs
: `1 k1 H# f% M. n- D
sub sp, sp, #S_FRAME_SIZE1 \2 m: I4 A) ]' H# a4 n
stmia sp, {r0 - r12}   @ Calling r0-r125 ~$ o2 f, C6 B
ldr r2, _armboot_start
2 M) [* e' Q$ t9 o
sub r2, r2, #(CONFIG_STACKSIZE)
" O7 P: E* l7 g2 t& m$ b2 s. o2 {
sub r2, r2, #(CONFIG_SYS_MALLOC_LEN)
6 d5 H5 d# E* f! W; m. i
sub r2, r2, #(CONFIG_SYS_GBL_DATA_SIZE+8)  @ set base 2 words into abort stack( `) U4 |4 g8 h& H1 X) w
ldmia r2, {r2 - r3}   @ get pc, cpsr( {* I- s1 V0 _/ \
add r0, sp, #S_FRAME_SIZE  @ restore sp_SVC
add r5, sp, #S_SP* [( H6 m% [2 P5 e2 Y, H
mov r1, lr
/ h! X  ^$ P. I% F, C6 V9 K
stmia r5, {r0 - r3}   @ save sp_SVC, lr_SVC, pc, cpsr3 f2 a6 r+ b- D
mov r0, sp
! H: i* h+ R* g$ x5 h
.endm
.macro irq_save_user_regs
& ]9 n1 q3 M$ V3 T
sub sp, sp, #S_FRAME_SIZE
- b1 u! e: \* F* W. a9 G
stmia sp, {r0 - r12}   @ Calling r0-r12
: _  T1 H" W9 @$ l5 d1 p
add     r7, sp, #S_PC' T! W7 v) [0 ]  m4 L6 E4 h9 g
stmdb   r7, {sp, lr}^                   @ Calling SP, LR. z, @9 W  ^9 [* `
str     lr, [r7, #0]                    @ Save calling PC
( R5 c" x1 K% F6 l) O. n
mrs     r6, spsr7 A6 `: J, l7 }- @8 \4 k
str     r6, [r7, #4]                    @ Save CPSR4 P3 ]. ^8 \/ P6 ~: N3 _
str     r0, [r7, #8]                    @ Save OLD_R05 t8 y+ t! f& `& A2 G
mov r0, sp$ U" I6 z7 D7 h! \+ ?0 L% C8 D/ _
.endm
.macro irq_restore_user_regs: r  f0 x2 ~4 f" L0 K5 L
ldmia sp, {r0 - lr}^   @ Calling r0 - lr! m. w6 @+ t& Q( r8 n. J% M
mov r0, r05 U( r2 Q3 S( k: ^
ldr lr, [sp, #S_PC]   @ Get PC
% [& k" j# t/ m3 G
add sp, sp, #S_FRAME_SIZE/ T1 e: r% Z) i8 @7 h2 a
subs pc, lr, #4   @ return & move spsr_svc into cpsr
1 x* I, s1 o; o& g
.endm
.macro get_bad_stack
. g2 g0 j$ A8 p' k& @* ?
ldr r13, _armboot_start  @ setup our mode stack
+ H  l+ C/ a; a
sub r13, r13, #(CONFIG_STACKSIZE)
3 @. ?8 d9 i; J# o1 ?1 W6 q
sub r13, r13, #(CONFIG_SYS_MALLOC_LEN)
% }4 h4 a5 s( X+ l
sub r13, r13, #(CONFIG_SYS_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack
str lr, [r13]   @ save caller lr / spsr
$ a" h! f" M& C9 f+ x
mrs lr, spsr3 E, Z6 b8 M! K$ B6 q
str     lr, [r13, #4]
mov r13, #MODE_SVC   @ prepare SVC-Mode% ~# k) G/ j! B, @; l+ h5 ~
@ msr spsr_c, r13$ Z4 @, w% w5 r: S$ I
msr spsr, r13
6 `, g  s. H' a: ~
mov lr, pc
; W8 \; ^2 b5 U" l- e& r1 y
movs pc, lr
$ F' ?( T, h- b6 t
.endm
.macro get_irq_stack   @ setup IRQ stack6 n; X  ^6 G% U
ldr sp, IRQ_STACK_START- o5 B! Q7 J: ~
.endm
.macro get_fiq_stack   @ setup FIQ stack  K$ v# A& o( a4 Y
ldr sp, FIQ_STACK_START+ |' ~' D# _, |; D
.endm
/*********************************************************
" r- p0 ]7 u. p2 K/ z6 l# M
* exception handlers
' O3 P" V" @" G8 W* V: y
********************************************************/
) b( d, v, W* P% F
@异常向量处理
3 _; p4 J8 ~6 l4 y% ?
@每一个异常向量处其实只放了一条跳转指令(因为每个异常向量只 @有4个字节不能放太多的程序),跳到相应的异常处理程序中。
4 I5 e) H2 ~' `; G2 m" I
.align  5
7 }4 `: F; m# |  g* ]" _, o
undefined_instruction:
5 _% ^! ?3 U, h8 [8 G
get_bad_stack
) l. v6 G, B3 \1 s& q- h
bad_save_user_regs$ u9 X- @! S; q
bl do_undefined_instruction
.align 5- f4 a& b9 o* o: V8 L, o
software_interrupt:, b: ?% Y$ @8 W! l! z! m7 e" P
get_bad_stack% h0 T, w& S7 L8 G# X  q3 H
bad_save_user_regs
& u+ a, l9 H: |6 d
bl do_software_interrupt
.align 5! I5 d( v3 d3 F5 B8 m- e
prefetch_abort:
5 ^9 i, P" g& ^
get_bad_stack0 t* I( A* \3 C
bad_save_user_regs
# a- R3 ?+ Y3 {9 w
bl do_prefetch_abort
.align 5! S5 c3 c9 ~  s0 x. [
data_abort:& E/ {, N& U; Q( {
get_bad_stack
' ^3 V3 W7 x/ |5 V$ V4 t2 x
bad_save_user_regs
6 @) s' S" m) {6 b
bl do_data_abort
.align 5, ~8 T$ V6 P! b7 v/ \
not_used:
' R# b& L9 j. m3 r: e4 P# n( p
get_bad_stack
  y% B9 D; A1 t6 \+ J7 V; G
bad_save_user_regs  X2 [! \' u  G. W4 w
bl do_not_used
#ifdef CONFIG_USE_IRQ
.align 5& i* M" y, r- R: i% M
irq:7 N5 q3 V9 ^0 J, b: K& q
get_irq_stack. s* ?) V6 d; R2 U( O9 V
irq_save_user_regs
) s  S1 O9 M$ [: s( [
bl do_irq! L" m9 e) _( I: u6 g1 C* B
irq_restore_user_regs
.align 5
( c3 w1 N! ^8 L; ^( l* v$ B
fiq:
3 A% ~' {( }' K
get_fiq_stack( _( N/ }  [  N0 h, v% V
/* someone ought to write a more effiction fiq_save_user_regs */
) Y6 L/ i2 |2 R' s/ z% M2 ?
irq_save_user_regs" ]6 j7 Q8 E- S5 z  Y: y( G
bl do_fiq5 I( q) c4 i) _' Y2 |+ c
irq_restore_user_regs
#else
.align 5
- m0 u' z; b1 q$ ~; @  O. {9 ~+ l
irq:
% b' F1 t- q7 ~4 l
get_bad_stack
3 I! g" n; I9 A5 h6 J
bad_save_user_regs' i0 O3 L/ p3 E; c. z, b7 K
bl do_irq
.align 5
9 g. m( L: z$ [; G5 W. `: C( ^
fiq:8 F, b3 \  o3 G! R( E& N8 s, k2 j
get_bad_stack
1 y, _7 P# ]" @+ `' N
bad_save_user_regs6 o3 p( r7 L4 }
bl do_fiq
#endif /*CONFIG_USE_IRQ*/9 v4 r( h3 X# U: Q. p9 h8 [
@可知start.S的流程为:异常向量——上电复位后进入复位异常向量——跳到启动代码处——设置处理器进入管理模式——关闭看门狗——关闭中断——设置时钟分频——关闭MMU和CACHE——进入lowlever_init.S——检查当前代码所处的位置,如果在FLASH中就将代码搬移到RAM中
. A  R: U, x2 Y
 楼主| 发表于 2017-1-13 17:30 | 显示全部楼层
我们知道,bootloader是系统上电后最初加载运行的代码。它提供了处理器上电复位后最开始需要执行的初始化代码。
   在PC机上引导程序一般由BIOS开始执行,然后读取硬盘中位于MBR(Main BootRecord,主引导记录)中的Bootloader(例如LILO或GRUB),并进一步引导操作系统的启动。
   然而在嵌入式系统中通常没有像BIOS那样的固件程序,因此整个系统的加载启动就完全由bootloader来完成。它主要的功能是加载与引导内核映像
一个嵌入式的存储设备通过通常包括四个分区:
第一分区:存放的当然是u-boot
第二个分区:存放着u-boot要传给系统内核的参数
第三个分区:是系统内核(kernel)
第四个分区:则是根文件系统
如下图所示:
-------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------
u-boot是一种普遍用于嵌入式系统中的Bootloader。
Bootloader介绍
Bootloader是进行嵌入式开发必然会接触的一个概念,它是嵌入式学院<<ahref="http://www.embedu.org/courses/course1.htm"target="_blank">嵌入式工程师职业培训班>二期课程中嵌入式Linux系统开发方面的重要内容。本篇文章主要讲解Bootloader的基本概念以及内部原理,这部分内容的掌握将对嵌入式linux系统开发的学习非常有帮助!
Bootloader的定义:Bootloader是在操作系统运行之前执行的一小段程序,通过这一小段程序,我们可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备。意思就是说如果我们要想让一个操作系统在我们的板子上运转起来,我们就必须首先对我们的板子进行一些基本配置和初始化,然后才可以将操作系统引导进来运行。具体在Bootloader中完成了哪些操作我们会在后面分析到,这里我们先来回忆一下PC的体系结构:PC机中的引导加载程序是由BIOS和位于硬盘MBR中的OSBoot Loader(比如LILO和GRUB等)一起组成的,BIOS在完成硬件检测和资源分配后,将硬盘MBR中的BootLoader读到系统的RAM中,然后将控制权交给OS Boot Loader。BootLoader的主要运行任务就是将内核映象从硬盘上读到RAM中,然后跳转到内核的入口点去运行,即开始启动操作系统。在嵌入式系统中,通常并没有像BIOS那样的固件程序(注:有的嵌入式cpu也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。比如在一个基于ARM7TDMIcore的嵌入式系统中,系统在上电或复位时通常都从地址0x00000000处开始执行,而在这个地址处安排的通常就是系统的BootLoader程序。(先想一下,通用PC和嵌入式系统为何会在此处存在如此的差异呢?)
Bootloader是基于特定硬件平台来实现的,因此几乎不可能为所有的嵌入式系统建立一个通用的Bootloader,不同的处理器架构都有不同的Bootloader,Bootloader不但依赖于cpu的体系结构,还依赖于嵌入式系统板级设备的配置。对于2块不同的板子而言,即使他们使用的是相同的处理器,要想让运行在一块板子上的Bootloader程序也能运行在另一块板子上,一般也需要修改Bootloader的源程序。
Bootloader的启动方式
Bootloader的启动方式主要有网络启动方式、磁盘启动方式和Flash启动方式。
1、网络启动方式
http://hi.csdn.net/attachment/201111/7/0_1320661397uCUV.gifUboot 详解" style="border: none; max-width: 602px; height: auto;">4 s: M+ ?: T* [
图1  Bootloader网络启动方式示意图
如图1所示,里面主机和目标板,他们中间通过网络来连接,首先目标板的DHCP/BIOS通过BOOTP服务来为Bootloader分配IP地址,配置网络参数,这样才能支持网络传输功能。我们使用的u-boot可以直接设置网络参数,因此这里就不用使用DHCP的方式动态分配IP了。接下来目标板的Bootloader通过TFTP服务将内核映像下载到目标板上,然后通过网络文件系统来建立主机与目标板之间的文件通信过程,之后的系统更新通常也是使用BootLoader的这种工作模式。工作于这种模式下的Boot Loader通常都会向它的终端用户提供一个简单的命令行接口。
2、磁盘启动方式
这种方式主要是用在台式机和服务器上的,这些计算机都使用BIOS引导,并且使用磁盘作为存储介质,这里面两个重要的用来启动linux的有LILO和GRUB,这里就不再具体说明了。
3、Flash启动方式
这是我们最常用的方式。Flash有NOR Flash和NAND Flash两种。NORFlash可以支持随机访问,所以代码可以直接在Flash上执行,Bootloader一般是存储在Flash芯片上的。另外Flash上还存储着参数、内核映像和文件系统。这种启动方式与网络启动方式之间的不同之处就在于,在网络启动方式中,内核映像和文件系统首先是放在主机上的,然后经过网络传输下载进目标板的,而这种启动方式中内核映像和文件系统则直接是放在Flash中的,这两点在我们u-boot的使用过程中都用到了。
U-boot的定义
U-boot,全称Universal BootLoader,是由DENX小组的开发的遵循GPL条款的开放源码项目,它的主要功能是完成硬件设备初始化、操作系统代码搬运,并提供一个控制台及一个指令集在操作系统运行前操控硬件设备。U-boot之所以这么通用,原因是他具有很多特点:开放源代码、支持多种嵌入式操作系统内核、支持多种处理器系列、较高的稳定性、高度灵活的功能设置、丰富的设备驱动源码以及较为丰富的开发调试文档与强大的网络技术支持。另外u-boot对操作系统和产品研发提供了灵活丰富的支持,主要表现在:可以引导压缩或非压缩系统内核,可以灵活设置/传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与产品发布,支持多种文件系统,支持多种目标板环境参数存储介质,采用CRC32校验,可校验内核及镜像文件是否完好,提供多种控制台接口,使用户可以在不需要ICE的情况下通过串口/以太网/USB等接口下载数据并烧录到存储设备中去(这个功能在实际的产品中是很实用的,尤其是在软件现场升级的时候),以及提供丰富的设备驱动等。
u-boot源代码的目录结构
1、board中存放于开发板相关的配置文件,每一个开发板都以子文件夹的形式出现。
2、Commom文件夹实现u-boot行下支持的命令,每一个命令对应一个文件。
3、cpu中存放特定cpu架构相关的目录,每一款cpu架构都对应了一个子目录。
4、Doc是文档目录,有u-boot非常完善的文档。
5、Drivers中是u-boot支持的各种设备的驱动程序。
6、Fs是支持的文件系统,其中最常用的是JFFS2文件系统。
7、Include文件夹是u-boot使用的头文件,还有各种硬件平台支持的汇编文件,系统配置文件和文件系统支持的文件。
8、Net是与网络协议相关的代码,bootp协议、TFTP协议、NFS文件系统得实现。
9、Tooles是生成U-boot的工具。
对u-boot的目录有了一些了解后,分析启动代码的过程就方便多了,其中比较重要的目录就是/board、/cpu、/drivers和/include目录,如果想实现u-boot在一个平台上的移植,就要对这些目录进行深入的分析。
-------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------
                                                                                       什么是《编译地址》?什么是《运行地址》?
(一)编译地址: 32位的处理器,它的每一条指令是4个字节,以4个字节存储顺序,进行顺序执行,CPU是顺序执行的,只要没发生什么跳转,它会顺序进行执行行,编译器会对每一条指令分配一个编译地址,这是编译器分配的,在编译过程中分配的地址,我们称之为编译地址。7 W* r. h- f7 e& ~1 y5 L
(二)运行地址:是指程序指令真正运行的地址,是由用户指定的,用户将运行地址烧录到哪里哪里就是运行的地址
     比如有一个指令的编译地址是0x5,实际运行的地址是0x200,如果用户将指令烧到0x200上,那么这条指令的运行地址就是0x200,
     当编译地址和运行地址不同的时候会出现什么结果?结果是不能跳转,编译后会产生跳转地址,如果实际地址和编译后产生的地址不相等,那么就不能跳转。
    C语言编译地址:都希望把编译地址和实际运行地址放在一起的,但是汇编代码因为不需要做C语言到汇编的转换,可以认为的去写地址,所以直接写的就是他的运行地址这就是为什么任何bootloader刚开始会有一段汇编代码,因为起始代码编译地址和实际地址不相等,这段代码和汇编无关,跳转用的运行地址。                                                   
                                                            编译地址和运行地址如何来算呢?
   1.   假如有两个编译地址a=0x10,b=0x7,b的运行地址是0x300,那么a的运行地址就是b的运行地址加上两者编译地址的差值,a-b=0x10-0x7=0x3,
      a的运行地址就是0x300+0x3=0x303。
: T7 r$ m* a4 t& R; ~+ p2 x# S/ u   2.  假设uboot上两条指令的编译地址为a=0x33000007和b=0x33000001,这两条指令都落在bank6上,现在要计算出他们对应的运行地址,要找出运行地址的始地址,这个是由用户烧录进去的,假设运行地址的首地址是0x0,则a的运行地址为0x7,b为0x1,就是这样算出来的。4 v7 D8 Q) d+ |! K' f
; E. g; C% u4 g1 ~) Z
                                            为什么要分配编译地址?这样做有什么好处,有什么作用?9 b2 E! @  [$ s# @& c! K
       比如在函数a中定义了函数b,当执行到函数b时要进行指令跳转,要跳转到b函数所对应的起始地址上去,编译时,编译器给每条指令都分配了编译地址,如果编译器已经给分配了地址就可以直接进行跳转,查找b函数跳转指令所对应的表,进行直接跳转,因为有个编译地址和指令对应的一个表,如果没有分配,编译器就查找不到这个跳转地址,要进行计算,非常麻烦。
) u0 a) t; f. g+ d- [
! ~- y. \1 i9 N% ?) i                                                                      什么是《相对地址》?
& [9 ?, X; c  L! f7 L' l" _$ r! e
       以NOR Flash为例,NORFalsh是映射到bank0上面,SDRAM是映射到bank6上面,uboot和内核最终是在SDRAM上面运行,最开始我们是从NorFlash的零地址开始往后烧录,uboot中至少有一段代码编译地址和运行地址是不一样的,编译uboot或内核时,都会将编译地址放入到SDRAM中,他们最终都会在SDRAM中执行,刚开始uboot在NorFlash中运行,运行地址是一个低端地址,是bank0中的一个地址,但编译地址是bank6中的地址,这样就会导致绝对跳转指令执行的失败,所以就引出了相对地址的概念
                                                                     那么什么是相对地址呢?
    至少在bank0中uboot这段代码要知道不能用b+编译地址这样的方法去跳转指令,因为这段代码的编译地址和运行地址不一样,那如何去做呢?
   要去计算这个指令运行的真实地址,计算出来后再做跳转,应该是b+运行地址,不能出现b+编译地址,而是b+运行地址,而运行地址是算出来的。
- ?. n# B$ M9 K# x7 u5 f% ^8 Z& [0 t
   _TEXT_BASE:4 _8 K( Q" g. I3 A) W- V
  .word TEXT_BASE//0x33F80000,在board/config.mk中
! `  d' o- Z' J" J( p3 }这段话表示,用户告诉编译器编译地址的起始地址' F1 ~" k7 h- J% H; ^2 X
-------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------
[url=]     U-Boot[/url]工作过程
   
     大多数 Boot Loader都包含两种不同的操作模式:"启动加载"模式和"下载"模式,这种区别仅对于开发人员才有意义。
    但从最终用户的角度看,Boot Loader的作用就是:用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。  B( ~+ |0 N( v+ q1 t
    (一)启动加载(Boot loading)模式:这种模式也称为"自主"(Autonomous)模式。
    也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。
    这种模式是 Boot Loader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader显然必须工作在这种模式下。

* o* {: N; `" b' \  g/ h: d0 V(二)下载(Downloading)模式:在这种模式下,目标机上的 Boot Loader将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被Boot Loader保存到目标机的RAM 中,然后再被 BootLoader写到目标机上的FLASH类固态存储设备中。Boot Loader的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 Boot Loader的这种工作模式。工作于这种模式下的 Boot Loader通常都会向它的终端用户提供一个简单的命令行接口。这种工作模式通常在第一次安装内核与跟文件系统时使用。或者在系统更新时使用。进行嵌入式系统调试时一般也让bootloader工作在这一模式下。3 R0 V/ h- {1 O
         U-Boot 这样功能强大的 Boot Loader 同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。
        大多数 bootloader 都分为阶段 1(stage1)和阶段 2(stage2)两大部分,u-boot 也不例外。依赖于 CPU体系结构的代码(如 CPU 初始化代码等)通常都放在阶段 1 中且通常用汇编语言实现,而阶段 2 则通常用 C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。) u, y1 T. x5 l- C9 c
-------------------------------------------------------------------------------------------------------------------------------------------
第一、大概总结性得的分析
      系统启动的入口点。既然我们现在要分析u-boot的启动过程,就必须先找到u-boot最先实现的是哪些代码,最先完成的是哪些任务。
      另一方面一个可执行的image必须有一个入口点,并且只能有一个全局入口点,所以要通知编译器这个入口在哪里。由此我们可以找到程序的入口点是在/board/lpc2210/u-boot.lds中指定的,其中ENTRY(_start)说明程序从_start开始运行,而他指向的是cpu/arm7tdmi/start.o文件。
因为我们用的是ARM7TDMI的cpu架构,在复位后从地址0x00000000取它的第一条指令,所以我们将Flash映射到这个地址上,
这样在系统加电后,cpu将首先执行u-boot程序。u-boot的启动过程是多阶段实现的,分了两个阶段。
依赖于cpu体系结构的代码(如设备初始化代码等)通常都放在stage1中,而且通常都是用汇编语言来实现,以达到短小精悍的目的。
而stage2则通常是用C语言来实现的,这样可以实现复杂的功能,而且代码具有更好的可读性和可移植性。
下面我们先详细分析下stage1中的代码,如图2所示:
http://hi.csdn.net/attachment/201111/7/0_13206614182wMG.gifUboot 详解" style="border: none; max-width: 602px; height: auto;"> ; x" O$ S0 y4 q: l
图2  Start.s程序流程
         代码真正开始是在_start,设置异常向量表,这样在cpu发生异常时跳转到/cpu/arm7tdmi/interrupts中去执行相应中断代码
        在interrupts文件中大部分的异常代码都没有实现具体的功能,只是打印一些异常消息,其中关键的是reset中断代码,跳到reset入口地址
         reset复位入口之前有一些段的声明
          1.在reset中,首先是将cpu设置为svc32模式下,并屏蔽所有irq和fiq。
          2.在u-boot中除了定时器使用了中断外其他的基本上都不需要使用中断,比如串口通信和网络等通信等,在u-boot中只要完成一些简单的通信就可以了,所以在这里屏蔽掉了所有的中断响应。
         3.初始化外部总线。这部分首先设置了I/O口功能,包括串口、网络接口等的设置,其他I/O口都设置为GPIO。然后设置BCFG0~BCFG3,即外部总线控制器。这里bank0对应Flash,设置为16位宽度,总线速度设为最慢,以实现稳定的操作;Bank1对应DRAM,设置和Flash相同;Bank2对应RTL8019。
        4.接下来是cpu关键设置,包括系统重映射(告诉处理器在系统发生中断的时候到外部存储器中去读取中断向量表)和系统频率。
        5.lowlevel_init,设定RAM的时序,并将中断控制器清零。这些部分和特定的平台有关,但大致的流程都是一样的。
       下面就是代码的搬移阶段了。为了获得更快的执行速度
       通常把stage2加载到RAM空间中来执行,因此必须为加载BootLoader的stage2准备好一段可用的RAM空间范围。空间大小最好是memory page大小(通常是4KB)的倍数
       一般而言,1M的RAM空间已经足够了。
        flash中存储的u-boot可执行文件中,代码段、数据段以及BSS段都是首尾相连存储的,
       所以在计算搬移大小的时候就是利用了用BSS段的首地址减去代码的首地址,这样算出来的就是实际使用的空间。
       程序用一个循环将代码搬移到0x81180000,即RAM底端1M空间用来存储代码。
       然后程序继续将中断向量表搬到RAM的顶端。由于stage2通常是C语言执行代码,所以还要建立堆栈去。
      在堆栈区之前还要将malloc分配的空间以及全局数据所需的空间空下来,他们的大小是由宏定义给出的,可以在相应位置修改。
基本内存分布图:
http://hi.csdn.net/attachment/201111/7/0_1320661431vvMa.gifUboot 详解" style="border: none; max-width: 602px; height: auto;">9 S8 ~! S; Y  m# ^
图3  搬移后内存分布情况图
    下来是u-boot启动的第二个阶段,是用c代码写的,
    这部分是一些相对变化不大的部分,我们针对不同的板子改变它调用的一些初始化函数,并且通过设置一些宏定义来改变初始化的流程
    所以这些代码在移植的过程中并不需要修改,也是错误相对较少出现的文件。
    在文件的开始先是定义了一个函数指针数组,通过这个数组,程序通过一个循环来按顺序进行常规的初始化,并在其后通过一些宏定义来初始化一些特定的设备。
    在最后程序进入一个循环,main_loop这个循环接收用户输入的命令,以设置参数或者进行启动引导
本篇文章将分析重点放在了前面的start.s上,是因为这部分无论在移植还是在调试过程中都是最容易出问题的地方,要解决问题就需要程序员对代码进行修改,所以在这里简单介绍了一下start.s的基本流程,希望能对大家有所帮助
-------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------
第二、代码分析
2.2 阶段 1 介绍8 L( r, ]% c- q7 H, k4 M) W% k0 G' _
u-boot 的 stage1 代码通常放在 start.s 文件中,它用汇编语言写成,其主要代码部分如下:
% k( N9 t: {( J; Q/ a7 f) z2.2.1 定义入口4 \' r7 r1 E" N' e) m' y
由于一个可执行的 Image 必须有一个入口点,并且只能有一个全局入口,通常这个入口放在 ROM(Flash)的 0x0. u% j9 K+ w" _" d0 M1 U
地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。  W8 Y" E2 l+ R: h: Q: U- m+ I
1. board/crane2410/u-boot.lds: ENTRY(_start)   ==>cpu/arm920t/start.S: .globl _start
6 K0 U1 l+ a) U2. uboot 代码区(TEXT_BASE = 0x33F80000)定义在board/crane2410/config.mk
U-Boot启动内核的过程可以分为两个阶段,两个阶段的功能如下:
      (1)第一阶段的功能
&#216;  硬件设备初始化
&#216;  加载U-Boot第二阶段代码到RAM空间
&#216;  设置好栈
&#216;  跳转到第二阶段代码入口
      (2)第二阶段的功能
&#216;  初始化本阶段使用的硬件设备
&#216;  检测系统内存映射
&#216;  将内核从Flash读取到RAM中
&#216;  为内核设置启动参数
&#216;  调用内核
[url=]1.1.1            U-Boot[/url]启动第一阶段代码分析
      第一阶段对应的文件是cpu/arm920t/start.S和board/samsung/mini2440/lowlevel_init.S。
      U-Boot启动第一阶段流程如下:
http://images.cnblogs.com/cnblogs_com/heaad/image001.pngUboot 详解" height="326" width="554" style="border: none; max-width: 602px; height: auto;">
详细分析
http://hi.csdn.net/attachment/201111/7/0_13206614182wMG.gifUboot 详解" style="border: none; max-width: 602px; height: auto;">
图 2.1 U-Boot启动第一阶段流程
     根据cpu/arm920t/u-boot.lds中指定的连接方式
    看一下uboot.lds文件,在board/smdk2410目录下面,uboot.lds是告诉编译器这些段改怎么划分,GUN编译过的段,最基本的三个段是RO,RW,ZI,RO表示只读,对应于具体的指代码段,RW是数据段,ZI是归零段,就是全局变量的那段。Uboot代码这么多,如何保证start.s会第一个执行,编译在最开始呢?就是通过uboot.lds链接文件进行
( d2 C3 @" E& F4 S+ S% W% D3 B6 c, c" }9 @" {% A' ]
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm","elf32-littlearm")+ N7 w9 _; a8 l! b# e
6 k3 a& k) J' G" C+ e6 Z- Z1 z% z
OUTPUT_ARCH(arm)
% S+ D) r+ V( F# u) FENTRY(_start)2 G; x4 g* ?! u! V6 J
SECTIONS
& ?, Z, _5 u3 h3 N; y# ]) T8 Y{! R# j- ]4 O5 C" k/ k& c- W5 ]
. = 0x00000000; //起始地址: Z" z* N( q& P+ ^: Y+ C. f

% V6 r( c+ G' |' c( x$ G/ n. = ALIGN(4); //4字节对齐; B; W9 C- g2 S, z1 n
.text : //test指代码段,上面3行标识是不占用任何空间的2 w2 X7 x8 [3 c" ~- }
{
" a7 u' X# S7 a, \$ hcpu/arm920t/start.o (.text) //这里把start.o放在第一位就表示把start.s编4 L/ e& V9 f4 C1 B: F. H
译时放到最开始,这就是为什么把uboot烧到起始地址上它肯定运行的是start.s1 w' X% f( `7 i" f. f$ S/ O
*(.text)" ~- ], R2 F3 l7 G) P
}2 ^1 y5 G$ W: {' D5 U& C

8 N) p6 }5 q! F4 ]+ U$ x" T. = ALIGN(4); //前面的 “.” 代表当前值,是计算一个当前的值,是计算上
; g4 d# q8 @: \8 e' u4 F面占用的整个空间,再加一个单元就表示它现在的位置
# F# `2 N4 I- h) p, v.rodata : { *(.rodata) }3 k0 S% O" \% h; C8 A3 c+ X& }

$ I; M$ N, ~! N. = ALIGN(4);
, F7 F4 V) k' ?6 S- g. U4 F# V.data : { *(.data) }
0 f' }9 a9 H) X/ ?5 K: X8 D7 X8 W
2 m# s: W7 {/ j2 r$ w7 [. = ALIGN(4);+ E  e0 X1 f0 b; w+ ]0 U7 H9 J
.got : { *(.got) }9 W3 z( R. M+ D" e

" j1 U+ _+ y2 I" j, b" g. = .;
$ }, `, A& S# n; G% D0 @- s__u_boot_cmd_start = .;& E$ i) `0 W; z8 e. O4 n. ]- {0 ?
.u_boot_cmd : { *(.u_boot_cmd) }
5 U! W' C0 W; Y__u_boot_cmd_end = .;
" R, p( e$ k- j7 X' \# Y- i( s! E- X! q% A8 ~. u6 z  L0 Y
. = ALIGN(4);: W$ |( l1 e. h% t: o
__bss_start = .; //bss表示归零段
7 m5 ?9 U  Y! E% G$ _2 j6 O8 R.bss : { *(.bss) }) `" @* P: X0 ?0 z
_end = .;) V2 ^+ M1 S9 V3 s! l" Q- m" K
}0 }4 u( ~; B6 Q1 D  ?/ Z# r
      第一个链接的是cpu/arm920t/start.o,因此u-boot.bin的入口代码在cpu/arm920t/start.o中,其源代码在cpu/arm920t/start.S中。下面我们来分析cpu/arm920t/start.S的执行。
1.     硬件设备初始化
(1)设置异常向量
         下面代码是系统启动后U-boot上电后运行的第一段代码,它是什么意思?
         u-boot对应的第一阶段代码放在cpu/arm920t/start.S文件中,入口代码如下:.
globl_startglobal                                   
_start:   b    start_code                   b是不带返回的跳转(bl是带返回的跳转),意思是无条件直接跳转到start_code标号出执行程序
      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:  .wordsoftware_interrupt
_prefetch_abort:  .word prefetch_abort
_data_abort:       .word data_abort
_not_used:         .word not_used
_irq:                    .word irq
_fiq:                    .word fiq
word伪操作用于分配一段字内存单元(分配的单元都是字对齐的),并用伪操作中的expr初始化
      .balignl 16,0xdeadbeef
      他们是系统定义的异常,一上电程序跳转到start_code异常处执行相应的汇编指令,下面定义出的都是不同的异常,比如软件发生软中断时,CPU就会去执行软中断的指令,这些异常中断在CUP中地址是从0开始,每个异常占4个字节
      ldr pc,_undefined_instruction表示把_undefined_instruction存放的数值存放到pc指针上
                  _undefined_instruction: .wordundefined_instruction表示未定义的这个异常是由.word来定义的,它表示定义一个字,一个32位的数
. word后面的数:表示把该标识的编译地址写入当前地址,标识是不占用任何指令的。把标识存放的数值copy到指针pc上面,那么标识上存放的值是什么?
是由.word undefined_instruction来指定的,pc就代表你运行代码的地址,她就实现了CPU要做一次跳转时的工作
/ @4 t2 V9 Y8 t7 y. Z1 v. q9 b, @9 N7 i
      以上代码设置了ARM异常向量表,各个异常向量介绍如下:
表 2.1ARM异常向量表
       地址 - p4 {. {" y- y: D4 H' D
  异常
# @9 W1 f& y! W* @5 T  E
    进入模式
8 s( t6 V7 p6 q5 F- h) [
描述
0x00000000 0 m" i# g8 c! V
复位! c/ a8 n& R4 n8 v3 ?
   管理模式) u# o) g! J6 H, h# d
   复位电平有效时,产生复位异常,程序跳转到复位处理程序处执行
; y! K' W7 W. k0 R% D4 x- J0 H
0x00000004
0 j4 \: I+ ^! n+ l' n
未定义指令
' G# ]1 i' c% s( u! D. c
   未定义模式0 _9 a3 {( N% o4 H* ^
   遇到不能处理的指令时,产生未定义指令异常' n  l5 |9 y6 A9 E. X+ c
0x00000008; F! e! _) D& w# l
软件中断
5 M: a4 K+ \( U+ ?5 W: `4 X
   管理模式' [/ u; h1 \2 x3 s( ^, x0 E
   执行SWI指令产生,用于用户模式下的程序调用特权操作指令3 B% S$ v9 l4 K& U0 g; k4 b( ?3 W
0x0000000c% }2 _# s5 Q0 r7 w) g
预存指令
: p, X  a) y9 I! t" a- ^* z) G
   中止模式
" w1 y4 N9 b8 e6 F/ u
  处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取中止异常
  E. d+ T. {3 ^
0x00000010
1 R& Y2 g% U! p$ u( f+ \1 M
数据操作$ \" G  a$ Z! B, n
   中止模式
  O" Q  ]9 y$ `2 d
  处理器数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常
4 }) ]9 x6 h0 Z, `4 \7 K+ y
0x00000014( |& J, f9 D7 T+ L
未使用4 B5 K0 t# R( q! }1 ~0 W
   未使用# G; m# S& G2 |2 u
   未使用
% s$ G* w7 i4 n$ \
0x00000018
2 J# {* k& Q3 c: V0 H
IRQ" n) r$ h( N+ e3 O; }6 I
   IRQ
8 H4 I& o- L5 G& b+ a
   外部中断请求有效,且CPSR中的I位为0时,产生IRQ异常
0 B( T; |2 ^) @' Y* n+ i8 ^
0x0000001c
* ?  Y  p1 m% a1 x8 c; g
FIQ
$ D* l8 c0 }2 n5 @; U
   FIQ
  E, J6 a5 ]: E7 w: t
   快速中断请求引脚有效,且CPSR中的F位为0时,产生FIQ异常7 f8 V$ S4 `- P; ^
      
     在cpu/arm920t/start.S中还有这些异常对应的异常处理程序。当一个异常产生时,CPU根据异常号在异常向量表中找到对应的异常向量,然后执行异常向量处的跳转指令,CPU就跳转到对应的异常处理程序执行。
      其中复位异常向量的指令“b start_code”决定了U-Boot启动后将自动跳转到标号“start_code”处执行。
(2)CPU进入SVC模式
start_code:
      
      mrs r0, cpsr
      bic  r0, r0,#0x1f      
      orr   r0, r0,#0xd3            
      msr cpsr, r0
      以上代码将CPU的工作模式位设置为管理模式,即设置相应的CPSR程序状态字,并将中断禁止位和快中断禁止位置一,从而屏蔽了IRQ和FIQ中断。
      操作系统先注册一个总的中断,然后去查是由哪个中断源产生的中断,再去查用户注册的中断表,查出来后就去执行用户定义的用户中断处理函数。
7 x, S8 R2 e; g; n5 g) G: `& ^
(3)设置控制寄存器地址
# ifdefined(CONFIG_S3C2400)       ( I( e: C& i; O  ^+ S
#  define pWTCON0x15300000      
3 o3 W  u0 A  k/ o, A
#  define INTMSK 0x14400008      
#  defineCLKDIVN     0x14800014
& R' j4 k4 O$ z, w) c) j3 k
#else     
#  define pWTCON0x53000000              
#  define INTMSK 0x4A000008                    
#  define INTSUBMSK0x4A00001C     
#  defineCLKDIVN     0x4C000014               
# endif
      对与s3c2440开发板,以上代码完成了WATCHDOG,INTMSK,INTSUBMSK,CLKDIVN四个寄存器的地址的设置。各个寄存器地址参见参考文献[4]。
(4)关闭看门狗
      ldr   r0,=pWTCON  
      mov      r1,#0x0     
' a5 |7 M+ L# M
      str   r1,[r0]               
      以上代码向看门狗控制寄存器写入0,关闭看门狗。否则在U-Boot启动过程中,CPU将不断重启
为什么要关看门狗?
        就是防止,不同得两个以上得CPU,进行喂狗的时间间隔问题:说白了,就是你运行的代码如果超出喂狗时间,而你不关狗,就会导致,你代码还没运行完又得去喂狗,就这样反复得重启CPU,那你代码永远也运行不完,所以,得先关看门狗得原因,就是这样。
关狗---详细的原因:
     关闭看门狗,关闭中断,所谓的喂狗是每隔一段时间给某个寄存器置位而已,在实际中会专门启动一个线程或进程会专门喂狗,当上层软件出现故障时就会停止喂狗,
     停止喂狗之后,cpu会自动复位,一般都在外部专门有一个看门狗,做一个外部的电路,不在cpu内部使用看门狗,cpu内部的看门狗是复位的cpu
      当开发板很复杂时,有好几个cpu时,就不能完全让板子复位,但我们通常都让整个板子复位。看门狗每隔短时间就会喂狗,问题是在两次喂狗之间的时间间隔内,运行的代码的时间是否够用,两次喂狗之间的代码是否在两次喂狗的时间延迟之内,如果在延迟之外的话,代码还没改完就又进行喂狗,代码永远也改不完
(5)屏蔽中断
      
      mov      r1,#0xffffffff     
      ldr   r0,=INTMSK      
      str   r1,[r0]                  
      INTMSK是主中断屏蔽寄存器,每一位对应SRCPND(中断源引脚寄存器)中的一位,表明SRCPND相应位代表的中断请求是否被CPU所处理。
        根据参考文献4,INTMSK寄存器是一个32位的寄存器,每位对应一个中断,向其中写入0xffffffff就将INTMSK寄存器全部位置一,从而屏蔽对应的中断。
# if defined(CONFIG_S3C2440)
        ldr  r1,=0x7fff                  
       ldr  r0,=INTSUBMSK  
        str  r1,[r0]            
# endif
      INTSUBMSK每一位对应SUBSRCPND中的一位,表明SUBSRCPND相应位代表的中断请求是否被CPU所处理。
      根据参考文献4,INTSUBMSK寄存器是一个32位的寄存器,但是只使用了低15位。向其中写入0x7fff就是将INTSUBMSK寄存器全部有效位(低15位)置一,从而屏蔽对应的中断。
屏蔽所有中断,为什么要关中断?
中断处理中ldrpc是将代码的编译地址放在了指针上,而这段时间还没有搬移代码,所以编译地址上面没有这个代码,如果进行跳转就会跳转到空指针上面. [$ j# C4 U, n, E$ b/ Y) X. I& _
(6)设置MPLLCON,UPLLCON, CLKDIVN
# if defined(CONFIG_S3C2440)
#define MPLLCON  0x4C000004
#define UPLLCON  0x4C000008  
        ldr  r0,=CLKDIVN   ;设置时钟  H" ~+ O2 i* b* b+ i9 v' `0 J% n& c/ p
        mov  r1, #5
        str  r1, [r0]
        ldr  r0, =MPLLCON
        ldr  r1, =0x7F021
        str  r1, [r0]
   ldr  r0,=UPLLCON
        ldr  r1, =0x38022
        str  r1, [r0]
# else
      
      
      ldr   r0, =CLKDIVN
      mov      r1, #3
      str   r1, [r0]
#endif
      CPU上电几毫秒后,晶振输出稳定,FCLK=Fin(晶振频率),CPU开始执行指令。但实际上,FCLK可以高于Fin,为了提高系统时钟,需要用软件来启用PLL。这就需要设置CLKDIVN,MPLLCON,UPLLCON这3个寄存器。
      CLKDIVN寄存器用于设置FCLK,HCLK,PCLK三者间的比例,可以根据表2.2来设置。
表 2.2 S3C2440的CLKDIVN寄存器格式
                         CLKDIVN                       
               位               
  说明
                          初始值                    
HDIVN
[2:1]
     00 : HCLK = FCLK/1.8 y; O  k# R, }/ m' g9 a( U
      01: HCLK = FCLK/2.1 n" i2 X5 k8 j* X# k
     10 : HCLK = FCLK/4 (当 CAMDIVN[9] = 0 时): j+ d; V; G+ L5 k' u; c3 H
     HCLK= FCLK/8  (当 CAMDIVN[9] = 1 时)
' c' P6 [9 |' m- v     11 : HCLK = FCLK/3 (当 CAMDIVN[8] = 0 时)4 @3 y3 U- f& ^5 i2 H' p
     HCLK = FCLK/6 (当 CAMDIVN[8] = 1时)
# c) `3 Y( u9 q8 n, U" ^3 P4 u' ~, z
00
PDIVN
[0]
0: PCLK =HCLK/1   1: PCLK = HCLK/2
0
      设置CLKDIVN为5,就将HDIVN设置为二进制的10,由于CAMDIVN[9]没有被改变过,取默认值0,因此HCLK =FCLK/4。PDIVN被设置为1,因此PCLK= HCLK/2。因此分频比FCLK:HCLK:PCLK = 1:4:8 。
      MPLLCON寄存器用于设置FCLK与Fin的倍数。MPLLCON的位[19:12]称为MDIV,位[9:4]称为PDIV,位[1:0]称为SDIV。
      对于S3C2440,FCLK与Fin的关系如下面公式:
      MPLL(FCLK) = (2×m×Fin)/(p× )
      其中: m=MDIC+8,p=PDIV+2,s=SDIV
      MPLLCON与UPLLCON的值可以根据参考文献4中“PLL VALUE SELECTIONTABLE”设置。该表部分摘录如下:
表 2.3 推荐PLL值
      输入频率      
                       输出频率                        
                      MDIV                  
                        PDIV                     
                      SDIV                     
12.0000MHz
48.00 MHz
56(0x38)
2
2
12.0000MHz
405.00 MHz
127(0x7f)
2
1
      当mini2440系统主频设置为405MHZ,USB时钟频率设置为48MHZ时,系统可以稳定运行,因此设置MPLLCON与UPLLCON为:
      MPLLCON=(0x7f<<12) | (0x02<<4) | (0x01) = 0x7f021
      UPLLCON=(0x38<<12) | (0x02<<4) | (0x02) = 0x38022
默认频率为     FCLK:HCLK:PCLK = 1:2:4,默认 FCLK 的值为 120 MHz,该值为 S3C2410 手册的推荐值。
设置时钟分频,为什么要设置时钟?
起始可以不设,系统能不能跑起来和频率没有任何关系,频率的设置是要让外围的设备能承受所设置的频率,如果频率过高则会导致cpu操作外围设备失败
说白了:设置频率,就为了CPU能去操作外围设备
(7)关闭MMU,cache ------(也就是做bank的设置)! G; j1 i- R+ N+ d4 W1 W1 p; X/ ^- s
      接着往下看:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
      bl   cpu_init_crit  
) ~5 Z$ x$ \4 }( }# R* Y; r
#endif
      cpu_init_crit这段代码在U-Boot正常启动时才需要执行,若将U-Boot从RAM中启动则应该注释掉这段代码
      下面分析一下cpu_init_crit到底做了什么:
320  #ifndef CONFIG_SKIP_LOWLEVEL_INIT
321  cpu_init_crit:
322   
324     */
325    mov      r0, #0
326     mcr p15, 0,r0, c7, c7,0   
327     mcr p15, 0,r0, c8, c7, 0   
& S0 ?: T- Z0 _9 W& Q! T3 v2 m0 W
328
329   
332     mrc p15, 0,r0, c1, c0,0   
333    bic  r0, r0,#0x00002300   @ clear bits 13,9:8 (--V- --RS)
334    bic  r0, r0,#0x00000087   @ clear bits 7, 2:0(B--- -CAM)
335    orr   r0, r0,#0x00000002   @ set bit 2 (A)Align
336    orr   r0, r0,#0x00001000   @ set bit 12 (I)I-Cache
337     mcr p15, 0,r0, c1, c0,0   
338
339   
344    mov      ip, lr
345
346    bl   lowlevel_init
347
348    mov      lr, ip
349    mov      pc, lr
350  #endif
7 x/ u- i' x' K
              代码中的c0,c1,c7,c8都是ARM920T的协处理器CP15的寄存器。其中c7是cache控制寄存器,c8是TLB控制寄存器。325~327行代码将0写入c7、c8,使Cache,TLB内容无效。
      第332~337行代码关闭了MMU。这是通过修改CP15的c1寄存器来实现的,先看CP15的c1寄存器的格式(仅列出代码中用到的位):
表 2.3CP15的c1寄存器格式(部分)
     15      
      14      
  13     
      12      
    11      
   10     
      9      
       8      
      7      
    6      
      5      
     4     
      3     
        2     
    1   
       0        
.
.
V
I
.
.
R
S
B
.
.
.
.
C
A
M
      各个位的意义如下:
V :  表示异常向量表所在的位置,0:异常向量在0x00000000;1:异常向量在0xFFFF0000# b# h; d* h4 q. ?
I :  0 :关闭ICaches;1 :开启ICaches
) s- a" J. E/ f: W! [2 k' m  Y# SR、S : 用来与页表中的描述符一起确定内存的访问权限: |- o; t& k5 J' m
B :  0 :CPU为小字节序;1 : CPU为大字节序
  T3 `( h; v$ H9 n0 B$ \C :  0:关闭DCaches;1:开启DCaches
, k9 X4 _! E# Q! Y) r9 w5 OA :  0:数据访问时不进行地址对齐检查;1:数据访问时进行地址对齐检查' [8 `$ s( P7 i$ b! Y4 Q! a2 v
M :  0:关闭MMU;1:开启MMU
      332~337行代码将c1的 M位置零,关闭了MMU。
为什么要关闭catch和MMU呢?catch和MMU是做什么用的?
. M0 M% d  s3 y1 u+ D
   MMU是Memory Management Unit的缩写,中文名是内存管理单元,它是中央处理器(CPU)中用来管理虚拟存储器、物理存储器的控制线路
   同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权      
概述:
一,关catch
      catch和MMU是通过CP15管理的,刚上电的时候,CPU还不能管理他们
      上电的时候MMU必须关闭,指令catch可关闭,可不关闭,但数据catch一定要关闭
      否则可能导致刚开始的代码里面,去取数据的时候,从catch里面取,而这时候RAM中数据还没有catch过来,导致数据预取异常
二:关MMU
     因为MMU是;把虚拟地址转化为物理地址得作用
     而目的是设置控制寄存器,而控制寄存器本来就是实地址(物理地址),再使能MMU,不就是多此一举了吗?
详细分析---
     Catch是cpu内部的一个2级缓存,它的作用是将常用的数据和指令放在cpu内部,MMU是用来把虚实地址转换为物理地址用的
     我们的目的:是设置控制的寄存器,寄存器都是实地址(物理地址),如果既要开启MMU又要做虚实地址转换的话,中间还多一步,多此一举了嘛?
3 [% _  q+ u' |4 @: L
     先要把实地址转换成虚地址,然后再做设置,但对uboot而言就是起到一个简单的初始化的作用和引导操作系统,如果开启MMU的话,很麻烦,也没必要,所以关闭MMU.
( k1 ?: d8 s8 {; R7 `& v   
      说到catch就必须提到一个关键字Volatile,以后在设置寄存器时会经常遇到,他的本质:是告诉编译器不要对我的代码进行优化,作用是让编写者感觉不倒变量的变化情况(也就是说,让它执行速度加快吧)
      优化的过程:是将常用的代码取出来放到catch中,它没有从实际的物理地址去取,它直接从cpu的缓存中去取,但常用的代码就是为了感觉一些常用变量的变化
       优化原因:如果正在取数据的时候发生跳变,那么就感觉不到变量的变化了,所以在这种情况下要用Volatile关键字告诉编译器不要做优化,每次从实际的物理地址中去取指令,这就是为什么关闭catch关闭MMU
        但在C语言中是不会关闭catch和MMU的,会打开,如果编写者要感觉外界变化,或变化太快,从catch中取数据会有误差,就加一个关键字Volatile。
(8)初始化RAM控制寄存器
                    bl lowlevel_init下来初始化各个bank,把各个bank设置必须搞清楚,对以后移植复杂的uboot有很大帮助5 q; j, G0 n8 z9 K4 _4 `1 Y
                    设置完毕后拷贝uboot代码到4k空间,拷贝完毕后执行内存中的uboot代码
     其中的lowlevel_init就完成了内存初始化的工作,由于内存初始化是依赖于开发板的,因此lowlevel_init的代码一般放在board下面相应的目录中。对于mini2440,lowlevel_init在board/samsung/mini2440/lowlevel_init.S中定义如下:
45  #defineBWSCON  0x48000000      
  … …
129  _TEXT_BASE:
130    .word    TEXT_BASE       0x33F80000,board/config.mk中这段话表示,用户告诉编译器编译地址的起始地址
131
132  .globl lowlevel_init
133  lowlevel_init:
134   
135   
136   
137    ldr    r0, =SMRDATA
138    ldr   r1, _TEXT_BASE
139    sub  r0, r0,r1            
140    ldr   r1,=BWSCON  
141    add    r2, r0, #13*4
142  0:
143    ldr    r3, [r0],#4   
144    str    r3, [r1], #4
145    cmp    r2, r0
146    bne    0b
147
148   
149    mov      pc, lr
150
151     .ltorg
152
153
154 SMRDATA:           
155  .word  … …
156   .word  … …
… …
      lowlevel_init初始化了13个寄存器来实现RAM时钟的初始化。lowlevel_init函数对于U-Boot从NANDFlash或NOR Flash启动的情况都是有效的。
      U-Boot.lds链接脚本有如下代码:
      .text :
      {
                    cpu/arm920t/start.o   (.text)
              board/samsung/mini2440/lowlevel_init.o (.text)
               board/samsung/mini2440/nand_read.o (.text)
             … …
      }
  
      board/samsung/mini2440/lowlevel_init.o将被链接到cpu/arm920t/start.o后面,因此board/samsung/mini2440/lowlevel_init.o也在U-Boot的前4KB的代码中。
      U-Boot在NANDFlash启动时,lowlevel_init.o将自动被读取到CPU内部4KB的内部RAM中。因此第137~146行的代码将从CPU内部RAM中复制寄存器的值到相应的寄存器中。
      对于U-Boot在NORFlash启动的情况,由于U-Boot连接时确定的地址是U-Boot在内存中的地址,而此时U-Boot还在NORFlash中,因此还需要在NOR Flash中读取数据到RAM中。
      由于NORFlash的开始地址是0,而U-Boot的加载到内存的起始地址是TEXT_BASE,SMRDATA标号在Flash的地址就是SMRDATA-TEXT_BASE。
      综上所述,lowlevel_init的作用就是将SMRDATA开始的13个值复制给开始地址[BWSCON]的13个寄存器,从而完成了存储控制器的设置。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
问题一:如果换一块开发板有可能改哪些东西?% b# p- i% |% y  p3 \5 A. k
                  首先,cpu的运行模式,如果需要对cpu进行设置那就设置,管看门狗,关中断不用改,时钟有可能要改,如果能正常使用则不用改,关闭catch和MMU不用改,设置bank有可能要改。最后一步拷贝时看地址会不会变,如果变化也要改,执行内存中代码,地址有可能要改。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
( C# P% u. M; U2 B6 c* z# Q问题二:Nor Flash和NandFlash本质区别:
                 就在于是否进行代码拷贝,也就是下面代码所表述:无论是Nor Flash还是NandFlash,核心思想就是将uboot代码搬运到内存中去运行,但是没有拷贝bss后面这段代码,只拷贝bss前面的代码,bss代码是放置全局变量的。Bss段代码是为了清零,拷贝过去再清零重复操作9 o4 x0 f! H) ~' u6 x, P
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
(9)复制U-Boot第二阶段代码到RAM
      cpu/arm920t/start.S原来的代码是只支持从NOR Flash启动的,经过修改现在U-Boot在NORFlash和NAND Flash上都能启动了,实现的思路是这样的:
      bl   bBootFrmNORFlash
      cmp      r0,#0         
      beqnand_boot        
      b    stack_setup        
nand_boot:
stack_setup:      
      
      其中bBootFrmNORFlash函数作用是判断U-Boot是在NAND Flash启动还是NOR Flash启动,若在NORFlash启动则返回1,否则返回0。根据ATPCS规则,函数返回值会被存放在r0寄存器中,因此调用bBootFrmNORFlash函数后根据r0的值就可以判断U-Boot在NANDFlash启动还是NORFlash启动。bBootFrmNORFlash函数在board/samsung/mini2440/nand_read.c中定义如下:
int bBootFrmNORFlash(void)
{
    volatileunsigned int *pdw = (volatile unsigned int *)0;
    unsignedint dwVal;
  
    dwVal =*pdw;        
    *pdw =0x12345678;
    if (*pdw!=0x12345678)      
    {
       return1;     
    }
   else                                 
    {
       *pdw =dwVal;      
       return 0;
    }
}
    无论是从NOR Flash还是从NAND Flash启动,地址0处为U-Boot的第一条指令“b   start_code”。
      对于从NAND Flash启动的情况,其开始4KB的代码会被自动复制到CPU内部4K内存中,因此可以通过直接赋值的方法来修改。
      对于从NOR Flash启动的情况,NOR Flash的开始地址即为0,必须通过一定的命令序列才能向NORFlash中写数据,所以可以根据这点差别来分辨是从NAND Flash还是NORFlash启动:向地址0写入一个数据,然后读出来,如果发现写入失败的就是NOR Flash,否则就是NAND Flash。
      下面来分析NOR Flash启动部分代码:
208    adr  r0,_start            
209    ldr   r1,_TEXT_BASE           
210     cmp    r0,r1     
211     beqstack_setup
212
213    ldr   r2,_armboot_start  
: D+ s5 m" t4 B% r- ?) p/ i0 b5 c6 G
214    ldr   r3,_bss_start        
2 {# T$ Z3 c8 j6 v
215    sub  r2, r3,r2            
216     add r2, r0,r2            
217
218  copy_loop:
219    ldmia    r0!, {r3-r10}
220    stmia     r1!, {r3-r10}
221    cmp      r0,r2                  
222    ble  copy_loop
223    b    stack_setup        
      下面再来分析NAND Flash启动部分代码:
nand_boot:
    mov r1,#NAND_CTL_BASE
    ldr r2,=( (7<<12)|(7<<8)|(7<<4)|(0<<0) )
    str r2,[r1, #oNFCONF]  
      
    ldr r2,=( (1<<4)|(0<<1)|(1<<0) )
    str r2,[r1, #oNFCONT]
    ldr r2,=(0x6)         
str r2, [r1, #oNFSTAT]
      
    mov r2,#0xff           
    strb r2,[r1, #oNFCMD]
    mov r3,#0              
   
    ldr sp,DW_STACK_START  
    mov fp,#0              
   
    ldr r0,=TEXT_BASE     
    mov r1,#0x0            
    mov r2,#0x30000        
   bl nand_read_ll               
tst  r0,#0x0                    
beq stack_setup
bad_nand_read:
loop2: bloop2   //infinite loop
.align 2
DW_STACK_START: .word STACK_BASE+STACK_SIZE-4
      其中NAND_CTL_BASE,oNFCONF等在include/configs/mini2440.h中定义如下:
#define NAND_CTL_BASE 0x4E000000  // NAND Flash控制寄存器基址
#define STACK_BASE 0x33F00000    //base address of stack
#define STACK_SIZE 0x8000        //size of stack
#define oNFCONF 0x00     
#define oNFCONT 0x04     
#define oNFADDR 0x0c   
#define oNFDATA 0x10     
#define oNFCMD  0x08   
#define oNFSTAT 0x20      
#define oNFECC  0x2c            
      NAND Flash各个控制寄存器的设置在S3C2440的数据手册有详细说明,这里就不介绍了。
      代码中nand_read_ll函数的作用是在NANDFlash中搬运U-Boot到RAM,该函数在board/samsung/mini2440/nand_read.c中定义。
      NAND Flash根据page大小可分为2种: 512B/page和2048B/page的。这两种NANDFlash的读操作是不同的。因此就需要U-Boot识别到NANDFlash的类型,然后采用相应的读操作,也就是说nand_read_ll函数要能自动适应两种NAND Flash。
      参考S3C2440的数据手册可以知道:根据NFCONF寄存器的Bit3(AdvFlash (Read only))和Bit2(PageSize (Read only))可以判断NAND Flash的类型。Bit2、Bit3与NANDFlash的block类型的关系如下表所示:
表 2.4 NFCONF的Bit3、Bit2与NAND Flash的关系
                                 Bit2   Bit3                                
                              0                                
                                 1                                    
0
256 B/page
512 B/page
1
1024 B/page
2048 B/page
      由于的NAND Flash只有512B/page和2048B/page这两种,因此根据NFCONF寄存器的Bit3即可区分这两种NAND Flash了。
      完整代码见board/samsung/mini2440/nand_read.c中的nand_read_ll函数,这里给出伪代码:
int nand_read_ll(unsigned char *buf, unsigned long start_addr,int size)
{
//根据NFCONF寄存器的Bit3来区分2种NAND Flash
      if( NFCONF & 0x8 )      
      {
             ////////////////////////////////////
             读取2K block 的NAND Flash
             ////////////////////////////////////
      }
      else                     
      {
             /////////////////////////////////////
             读取512B block 的NAND Flash
             /////////////////////////////////////
      }
    return0;
}
(10)设置堆栈
      
stack_setup:
      ldr   r0,_TEXT_BASE           
      sub  r0, r0,#CONFIG_SYS_MALLOC_LEN  
      sub  r0, r0, #CONFIG_SYS_GBL_DATA_SIZE
#ifdef CONFIG_USE_IRQ
      sub  r0, r0,#(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
      sub  sp, r0,#12         
      只要将sp指针指向一段没有被使用的内存就完成栈的设置了。根据上面的代码可以知道U-Boot内存使用情况了,如下图所示:
http://images.cnblogs.com/cnblogs_com/heaad/image003.jpgUboot 详解" height="437" width="385" style="border: none; max-width: 602px; height: auto;">
图2.2 U-Boot内存使用情况
(11)清除BSS段
clear_bss:
      ldr   r0,_bss_start            
      ldr   r1,_bss_end              
      mov      r2, #0x00000000
clbss_l:str    r2,[r0]         
      add r0, r0, #4
      cmp    r0, r1
      ble  clbss_l
      初始值为0,无初始值的全局变量,静态变量将自动被放在BSS段。应该将这些变量的初始值赋为0,否则这些变量的初始值将是一个随机的值,若有些程序直接使用这些没有初始化的变量将引起未知的后果。
(12)跳转到第二阶段代码入口
      ldr   pc, _start_armboot
_start_armboot:  .word  start_armboot
      跳转到第二阶段代码入口start_armboot处。
[url=]1.1.2            U-Boot[/url]启动第二阶段代码分析
      start_armboot函数在lib_arm/board.c中定义,是U-Boot第二阶段代码的入口。U-Boot启动第二阶段流程如下:
http://images.cnblogs.com/cnblogs_com/heaad/QQ%E6%88%AA%E5%9B%BE%E6%9C%AA%E5%91%BD%E5%90%8D.jpgUboot 详解" height="745" width="621" style="border: none; max-width: 602px; height: auto;">
图 2.3 U-Boot第二阶段执行流程
      在分析start_armboot函数前先来看看一些重要的数据结构
(1)gd_t结构体
      U-Boot使用了一个结构体gd_t来存储全局数据区的数据,这个结构体在include/asm-arm/global_data.h中定义如下:
typedef struct    global_data {
      bd_t             *bd;
      unsignedlong     flags;
      unsignedlong     baudrate;
      unsignedlong     have_console;     
      unsignedlong     env_addr;   
      unsignedlong     env_valid;   
      unsignedlong     fb_base;
      void             **jt;            
} gd_t;
      U-Boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址:
#defineDECLARE_GLOBAL_DATA_PTR    register volatile gd_t *gd asm ("r8")
      DECLARE_GLOBAL_DATA_PTR定义一个gd_t全局数据结构的指针,这个指针存放在指定的寄存器r8中。这个声明也避免编译器把r8分配给其它的变量。任何想要访问全局数据区的代码,只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd指针来访问全局数据区了。
      根据U-Boot内存使用图中可以计算gd的值:
gd = TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)
(2)bd_t结构体
      bd_t在include/asm-arm.u/u-boot.h中定义如下:
typedef struct bd_info {
   int               bi_baudrate;            
    unsignedlong    bi_ip_addr;        
    structenvironment_s      *bi_env;            
   ulong          bi_arch_number;     
   ulong          bi_boot_params;      
   struct                        
    {
             ulong start;
             ulong size;
   }bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
      U-Boot启动内核时要给内核传递参数,这时就要使用gd_t,bd_t结构体中的信息来设置标记列表。
      第一阶段调用start_armboot指向C语言执行代码区,首先它要从内存上的重定位数据获得不完全配置的全局数据表格和板级信息表格,即获得gd_tbd_t
这两个类型变量记录了刚启动时的信息,并将要记录作为引导内核和文件系统的参数,如bootargs等等,并且将来还会在启动内核时,由uboot交由kernel时会有所用。+ C/ g& ]+ _2 O  K* ?+ D- Q1 v  z
(3)init_sequence数组
      U-Boot使用一个数组init_sequence来存储对于大多数开发板都要执行的初始化函数的函数指针。init_sequence数组中有较多的编译选项,去掉编译选项后init_sequence数组如下所示:
typedef int (init_fnc_t) (void);
init_fnc_t *init_sequence[] = {
      board_init,      
      timer_init,           
      env_init,         
      init_baudrate,     
      serial_init,           
      console_init_f,   
      display_banner,  
      dram_init,           
      display_dram_config,            
      NULL,
};
      其中的board_init函数在board/samsung/mini2440/mini2440.c中定义,该函数设置了MPLLCOM,UPLLCON,以及一些GPIO寄存器的值,还设置了U-Boot机器码和内核启动参数地址:
gd->bd->bi_arch_number = MACH_TYPE_MINI2440;
gd->bd->[url=]bi_boot_params[/url] =0x30000100;  
      其中的dram_init函数在board/samsung/mini2440/mini2440.c中定义如下:
int dram_init (void)
{
     
     gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
     gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
     return 0;
}
mini2440使用2片32MB的SDRAM组成了64MB的内存,接在存储控制器的BANK6,地址空间是0x30000000~0x34000000。
在include/configs/mini2440.h中PHYS_SDRAM_1和PHYS_SDRAM_1_SIZE分别被定义为0x30000000和0x04000000(64M)。
      分析完上述的数据结构,下面来分析start_armboot函数:
void start_armboot (void)
{
      init_fnc_t **init_fnc_ptr;
      char *s;
      … …
      
      gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN -sizeof(gd_t));
      … …
      memset ((void*)gd, 0, sizeof (gd_t));
      gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
      memset (gd->bd, 0, sizeof (bd_t));
      gd->flags |= GD_FLG_RELOC;
      monitor_flash_len = _bss_start - _armboot_start;
      for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr){
             if ((*init_fnc_ptr)() != 0) {
                    hang ();
             }
      }
      mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN,
                    CONFIG_SYS_MALLOC_LEN);
#ifndef CONFIG_SYS_NO_FLASH
      
      display_flash_config (flash_init ());
#endif
      … …
#if defined(CONFIG_CMD_NAND)
      puts ("NAND:  ");
      nand_init();        
#endif
      … …
      
      env_relocate ();
      … …
      
      gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
      stdio_init ();
      jumptable_init ();
      … …
      console_init_r ();
      … …
      
      enable_interrupts ();
#ifdef CONFIG_USB_DEVICE
      usb_init_slave();
#endif
      
      if ((s = getenv ("loadaddr")) != NULL) {
             load_addr = simple_strtoul (s, NULL, 16);
      }
#if defined(CONFIG_CMD_NET)
      if ((s = getenv ("bootfile")) != NULL) {
             copy_filename (BootFile, s, sizeof (BootFile));
      }
#endif
      … …
      
#if defined(CONFIG_CMD_NET)
#if defined(CONFIG_NET_MULTI)
      puts ("Net:   ");
#endif
      eth_initialize(gd->bd);
… …
#endif
      
      for (;;) {
             main_loop ();
      }
      
}
      main_loop函数在common/main.c中定义。一般情况下,进入main_loop函数若干秒内没有
[url=]1.1.3            U-Boot[/url]启动Linux过程
      U-Boot使用标记列表(taggedlist)的方式向Linux传递参数。标记的数据结构式是tag,在U-Boot源代码目录include/asm-arm/setup.h中定义如下:
struct tag_header {
      u32size;      
      u32tag;      
};
struct tag {
      struct tag_header hdr;
      union {
             structtag_core          core;
             structtag_mem32     mem;
             struct tag_videotext  videotext;
             structtag_ramdisk    ramdisk;
             struct tag_initrd  initrd;
             structtag_serialnr      serialnr;
             structtag_revision     revision;
             structtag_videolfb    videolfb;
             structtag_cmdline    cmdline;
            
             struct tag_acorn  acorn;
            
             structtag_memclk     memclk;
      } u;
};
      U-Boot使用命令bootm来启动已经加载到内存中的内核。而bootm命令实际上调用的是do_bootm函数。对于Linux内核,do_bootm函数会调用do_bootm_linux函数来设置标记列表和启动内核。do_bootm_linux函数在lib_arm/bootm.c中定义如下:
59   int do_bootm_linux(intflag, int argc, char *argv[], bootm_headers_t *images)
60   {
61     bd_t      *bd = gd->bd;
62     char      *s;
63     int   machid =bd->bi_arch_number;
64     void      (*theKernel)(int zero, int arch, uint params);
65  
66   #ifdefCONFIG_CMDLINE_TAG
67      char*commandline = getenv("bootargs");  
68   #endif
      … …
73      theKernel =(void (*)(int, int, uint))images->ep;
      … …
86   #if defined(CONFIG_SETUP_MEMORY_TAGS) || \
87      defined (CONFIG_CMDLINE_TAG) || \
88      defined (CONFIG_INITRD_TAG) || \
89      defined (CONFIG_SERIAL_TAG) || \
90      defined (CONFIG_REVISION_TAG) || \
91      defined (CONFIG_LCD) || \
92      defined (CONFIG_VFD)
93     setup_start_tag(bd);                                    
      … …
100  #ifdef CONFIG_SETUP_MEMORY_TAGS
101    setup_memory_tags(bd);                           
102  #endif
103  #ifdef CONFIG_CMDLINE_TAG
104    setup_commandline_tag (bd,commandline);     
105  #endif
      … …
113    setup_end_tag(bd);                                       
114  #endif
115
116   
117     printf("\nStarting kernel ...\n\n");
      … …
126    cleanup_before_linux();         
127
128     theKernel(0, machid,bd->bi_boot_params);     
129   
130
131     return1;
132  }
      其中的setup_start_tag,setup_memory_tags,setup_end_tag函数在lib_arm/bootm.c中定义如下:
      (1)setup_start_tag函数
static void setup_start_tag (bd_t *bd)
{
      params = (struct tag *)bd->bi_boot_params;
      params->hdr.tag = ATAG_CORE;
      params->hdr.size = tag_size (tag_core);
      params->u.core.flags = 0;
      params->u.core.pagesize = 0;
      params->u.core.rootdev = 0;
      params = tag_next (params);
}
      标记列表必须以ATAG_CORE开始,setup_start_tag函数在内核的参数的开始地址设置了一个ATAG_CORE标记。
      (2)setup_memory_tags函数
static void setup_memory_tags (bd_t *bd)
{
      int i;
      for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++){   
             params->hdr.tag = ATAG_MEM;
             params->hdr.size = tag_size (tag_mem32);
             params->u.mem.start = bd->bi_dram.start;
             params->u.mem.size = bd->bi_dram.size;
             params = tag_next (params);
      }
}
      setup_memory_tags函数设置了一个ATAG_MEM标记,该标记包含内存起始地址,内存大小这两个参数。
      (3)setup_end_tag函数
static void setup_end_tag (bd_t *bd)
{
      params->hdr.tag = ATAG_NONE;
      params->hdr.size = 0;
}
      标记列表必须以标记ATAG_NONE结束,setup_end_tag函数设置了一个ATAG_NONE标记,表示标记列表的结束。
      U-Boot设置好标记列表后就要调用内核了。但调用内核前,CPU必须满足下面的条件:
(1)   CPU寄存器的设置
&#216;  r0=0
&#216;  r1=机器码
&#216;  r2=内核参数标记列表在RAM中的起始地址
(2)   CPU工作模式
&#216;  禁止IRQ与FIQ中断
&#216;  CPU为SVC模式
(3)   使数据Cache与指令Cache失效
      do_bootm_linux中调用的cleanup_before_linux函数完成了禁止中断和使Cache失效的功能。cleanup_before_linux函数在cpu/arm920t/cpu.中定义:
int cleanup_before_linux (void)
{
      
      disable_interrupts();        
      
      icache_disable();              
      dcache_disable();            
      
      cache_flush();                  
      return 0;
}
      由于U-Boot启动以来就一直工作在SVC模式,因此CPU的工作模式就无需设置了。
do_bootm_linux中:
64     void      (*theKernel)(int zero, int arch, uint params);
… …
73      theKernel =(void (*)(int, int, uint))images->ep;
… …
128     theKernel(0, machid, bd->bi_boot_params);
      第73行代码将内核的入口地址“images->ep”强制类型转换为函数指针。根据ATPCS规则,函数的参数个数不超过4个时,使用r0~r3这4个寄存器来传递参数。因此第128行的函数调用则会将0放入r0,机器码machid放入r1,内核参数地址bd->bi_boot_params放入r2,从而完成了寄存器的设置,最后转到内核的入口地址。
      到这里,U-Boot的工作就结束了,系统跳转到Linux内核代码执行。
[url=]1.1.4            U-Boot[/url]添加命令的方法及U-Boot命令执行过程
      下面以添加menu命令(启动菜单)为例讲解U-Boot添加命令的方法。
(1)   建立common/cmd_menu.c
      习惯上通用命令源代码放在common目录下,与开发板专有命令源代码则放在board/目录下,并且习惯以“cmd_<命令名>.c”为文件名。
(2)   定义“menu”命令
      在cmd_menu.c中使用如下的代码定义“menu”命令:
_BOOT_CMD(
      menu,   3,   0,   do_menu,
      "menu - display a menu, to select the items to do something\n",
      " - display a menu, to select the items to do something"
);
      其中U_BOOT_CMD命令格式如下:
U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)
      各个参数的意义如下:
name:命令名,非字符串,但在U_BOOT_CMD中用“#”符号转化为字符串
maxargs:命令的最大参数个数
rep:是否自动重复(按Enter键是否会重复执行)
cmd:该命令对应的响应函数
usage:简短的使用说明(字符串)
help:较详细的使用说明(字符串)
      在内存中保存命令的help字段会占用一定的内存,通过配置U-Boot可以选择是否保存help字段。若在include/configs/mini2440.h中定义了CONFIG_SYS_LONGHELP宏,则在U-Boot中使用help命令查看某个命令的帮助信息时将显示usage和help字段的内容,否则就只显示usage字段的内容。
      U_BOOT_CMD宏在include/command.h中定义:
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs,rep, cmd, usage, help}
      “##”与“#”都是预编译操作符,“##”有字符串连接的功能,“#”表示后面紧接着的是一个字符串。
      其中的cmd_tbl_t在include/command.h中定义如下:
struct cmd_tbl_s {
      char             *name;         
      int         maxargs;      
      int         repeatable;   
      int         (*cmd)(struct cmd_tbl_s *, int, int, char*[]);
      char             *usage;        
#ifdef   CONFIG_SYS_LONGHELP
      char             *help;         
#endif
#ifdef CONFIG_AUTO_COMPLETE
      
      int         (*complete)(int argc, char *argv[], char last_char, int maxv, char*cmdv[]);
#endif
};
typedef struct cmd_tbl_s  cmd_tbl_t;
      一个cmd_tbl_t结构体变量包含了调用一条命令的所需要的信息。
      其中Struct_Section在include/command.h中定义如下:
#define Struct_Section  __attribute__((unused,section (".u_boot_cmd")))
      凡是带有__attribute__ ((unused,section(".u_boot_cmd"))属性声明的变量都将被存放在".u_boot_cmd"段中,并且即使该变量没有在代码中显式的使用编译器也不产生警告信息。
      在U-Boot连接脚本u-boot.lds中定义了".u_boot_cmd"段:
      . = .;
      __u_boot_cmd_start =.;         
      .u_boot_cmd : { *(.u_boot_cmd) }
      __u_boot_cmd_end =.;         
      这表明带有“.u_boot_cmd”声明的函数或变量将存储在“u_boot_cmd”段。这样只要将U-Boot所有命令对应的cmd_tbl_t变量加上“.u_boot_cmd”声明,编译器就会自动将其放在“u_boot_cmd”段,查找cmd_tbl_t变量时只要在__u_boot_cmd_start与__u_boot_cmd_end之间查找就可以了。
      因此“menu”命令的定义经过宏展开后如下:
cmd_tbl_t __u_boot_cmd_menu __attribute__ ((unused,section(".u_boot_cmd"))) = {menu, 3, 0, do_menu, "menu - display a menu,to select the items to do something\n", " - display a menu, toselect the items to do something"}
      实质上就是用U_BOOT_CMD宏定义的信息构造了一个cmd_tbl_t类型的结构体。编译器将该结构体放在“u_boot_cmd”段,执行命令时就可以在“u_boot_cmd”段查找到对应的cmd_tbl_t类型结构体。
(3)   实现命令的函数
      在cmd_menu.c中添加“menu”命令的响应函数的实现。具体的实现代码略:
int do_menu (cmd_tbl_t *cmdtp, int flag, int argc, char*argv[])
{
      
}
(4)   将common/cmd_menu.c编译进u-boot.bin
      在common/Makefile中加入如下代码:
COBJS-$(CONFIG_BOOT_MENU) += cmd_menu.o
      在include/configs/mini2440.h加入如代码:
#define CONFIG_BOOT_MENU 1
      重新编译下载U-Boot就可以使用menu命令了
(5)menu命令执行的过程
      在U-Boot中输入“menu”命令执行时,U-Boot接收输入的字符串“menu”,传递给run_command函数。run_command函数调用common/command.c中实现的find_cmd函数在__u_boot_cmd_start与__u_boot_cmd_end间查找命令,并返回menu命令的cmd_tbl_t结构。然后run_command函数使用返回的cmd_tbl_t结构中的函数指针调用menu命令的响应函数do_menu,从而完成了命令的执行。

: r  S' @. @% t4 i7 E
回复

使用道具 举报

发表于 2017-1-13 20:16 | 显示全部楼层
收藏6 h& r$ d1 u+ ]
2 L% ^: R8 u8 z) [; j$ i4 K
备查
9 d5 r  C2 F/ Z# H' }! R! z, V4 F) b' V0 ?, D0 X+ [: ^0 N
~~~~~~~~
回复

使用道具 举报

发表于 2017-1-14 16:24 | 显示全部楼层
技术文    收藏备用
回复

使用道具 举报

本版积分规则

QQ|一淘宝店|手机版|商店|一乐电子 ( 粤ICP备09076165号 ) 公安备案粤公网安备 44522102000183号

GMT+8, 2025-8-20 09:00 , Processed in 0.051325 second(s), 22 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表