linux源代码子目录包含boot,U-boot源码分析

之前也发表过关于《Bootloader启动过程分析》的文章,但是内容表达得比较抽象,大多是文字叙述,所以这里从系统和代码的角度来深入分析bootloader的启动过程。

U-Boot工程结构

学习一个软件,尤其是开源软件,首先应该从分析软件的工程结构开始。一个好的软件有良好的工程结构,对于读者学习和理解软件的架构以及工作流程都有很好的帮助。

U-Boot的源代码布局和Linux类似,使用了按照模块划分的结构,并且充分考虑了体系结构和跨平台问题。

U-Boot源代码目录结构

子目录名

作用

board

开发板相关的定义和结构

common

包含U-Boot用到的各种处理函数

cpu

各种不同类型的处理器相关代码

doc

U-Boot文档

drivers

常用外部设备驱动程序

examples

存放U-Boot开发代码样例

fs

文件系统有关的代码,包括cramfs、ext2、fat等常见文件系统

include

U-Boot用到的头文件

lib_arm

ARM体系结构有关的数据定义和操作

lib_generic

U-Boot通用的操作函数

net

常用的网络协议,包括bootp、rarp、arp、tftp等

post

上电自检相关代码

rtc

实时钟有关操作

tools

U-Boot有关的数据代码

U-Boot总体工作流程

与大多数Bootloader类似,U-Boot的启动分成stage1和stage2两个阶段。

stage1使用汇编语言编写,通常与CPU体系紧密相关,如处理器初始化和设备初始化代码等,该阶段在start.S文件中实现。

a4c26d1e5885305701be709a3d33442f.png

上图是U-Boot中Stage1工作流程。Stage1的代码都是与平台相关的,使用汇编语言编写占用空间小而且执行速度快。

Stage1负责建立Stage1阶段使用堆栈和代码段,然后复制Stage2阶段的代码到内存。

Stage2阶段一般包括:初始化Flash器件、swim

系统内存映射、初始化网络设备、进入命令循环,接收用户从串口发送的命令然后进行相应的处理。

Stage2使用C语言编写,用于加载操作系统内核,该阶段主要是board.c中是start_armboot()函数实现。下图为U-Boot的Stage1和Stage2在Flash和RAM中的分配。

a4c26d1e5885305701be709a3d33442f.png

从上图中可以看出,U-Boot在加载到内存后,使用了操作系统空余的内存空间。

U-Boot启动流程分析

a4c26d1e5885305701be709a3d33442f.png

从图中可以看出U-Boot的启动代码分布在start.S、low_level_init.S、board.c和main.c文件中

Start.S是U-Boot整个程序的入口,该文件使用汇编语言编写,不同体系结构的启动代码不同

low_level_init.S是特定开发板的设置代码;

board.c包含开发板底层设备驱动;

main.c是一个与平台无关的代码,U-Boot应用程序的入口在此文件中。

①_start标号

在U-Boot工程中,每种处理器目录下都有一个start.S文件,该文件中有一个_start标号,是整个U-Boot代码的入口点。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

.globl _start

_start:

b reset //复位向量:无条件跳转到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_used

_irq: .word irq

_fiq: .word fiq

.balignl

16,0xdeadbeef

_TEXT_BASE:

.word TEXT_BASE //定义整个錟-Boot镜像文件在内存加载的地址

.globl _armboot_start

_armboot_start:

.word

_start

.globl _bss_start

_bss_start:

.word

__bss_start //定义代码段起始

.globl _bss_end

_bss_end:

.word

_end //定义代码段结束地址

#ifdef CONFIG_USE_IRQ

.globl

IRQ_STACK_START //定义IRQ的堆栈地址

IRQ_STACK_START:

.word 0x0badc0de

.globl

FIQ_STACK_START //定义FIQ的堆栈地址

FIQ_STACK_START:

.word

0x0badc0de

#endif

_start标号下面的代码主要是一些伪指令,设置全局变量,供启动程序把U-Boot映像从Flash存储器复制到内存中。

其中比较重要的变量是TEXT_BASE,该变量是通过连接脚本得到的。TEXT_BASE变量需要根据开发板的情况自己修改,具体地址需要根据硬件设计确定。

_start标号一开始定义了ARM处理器7个中断向量的向量表,对应ARM处理器的7种模式。

由于上电一开始处理器会从0地址执行指令,因此第一个指令直接跳转到reset标号。

reset执行机器初始化的一些操作,此处的跳转指令,无论是冷启动还是热启动开发板都会执行reset标号的代码。

reset也属于一种异常模式,并且该模式的代码不需要返回。

②reset标号

reset标号的代码在处理器启动的时候最先被执行。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

reset:

mrs

r0,cpsr //保存CPSR寄存器的值到r0寄存器

bic

r0,r0,#0x1f //清除中断

orr

r0,r0,#0xd3

msr

cpsr,r0 //设置CPSR为超级保护模式

//关闭看门狗

#if

defined(CONFIG_S3C2400)

# define

pWTCON 0x15300000 //看门狗地址

# define

INTMSK 0x14400008 //中断控制器基址

# define

CLKDIVN 0x14800014

#elif

defined(CONFIG_S3C2410)

# define

pWTCON 0x53000000

# define

INTMSK 0x4A000008

# define

INTSUBMSK 0x4A00001C

# define

CLKDIVN 0x4C000014

#endif

#if defined(CONFIG_S3C2400)

|| defined(CONFIG_S3C2410)

ldr r0, =pWTCON //取出当前看门狗控制寄存器的地址到r0

mov r1,

#0x0 //设置r1寄存器的值为0

str r1,

[r0] //写入看门狗控制寄存器

mov

r1, #0xffffffff //设置r1

ldr

r0, =INTMSK //取出中断屏蔽寄存器地址到r0

str

r1,

[r0] //r1的值写入中断屏蔽寄存器

# if

defined(CONFIG_S3C2410)

ldr

r1, =0x3ff

ldr

r0, =INTSUBMSK

str

r1, [r0]

# endif

ldr

r0,

=CLKDIVN //取出时钟寄存器地址到r0

mov

r1, #3 //设置r1的值

str

r1,

[r0] //写入时钟配置

#endif

#ifndef

CONFIG_SKIP_LOWLEVEL_INIT

bl cpu_init_crit //跳转到开发板相关初始化代码

#endif

注意,最后根据CONFIG_SKIP_LOWLEVEL_INIT宏的值是否跳到cpu_init_crit标号执行。

请注意这里使用的是bl指令,在执行完cpu_init_crit标号的代码后会返回。

③cpu_init_crit标号

cpu_init_crit标号处的代码初始化ARM处理器关键的寄存器。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

#ifndef

CONFIG_SKIP_LOWLEVEL_INIT

cpu_init_crit:

mov

r0, #0

mcr

p15, 0, r0, c7, c7,

0 //刷新cache

mcr

p15, 0, r0, c8, c7,

0 //刷新TLB

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

mov

ip, lr

bl lowlevel_init //跳转到lowlevel_init

mov

lr, ip

mov

pc, lr

#endif

注意刷新cache和TLB。

cache是一种高速缓存存储器,用于保存CPU频繁使用的数据,在使用Cache技术的处理器上,当一条指令要访问内存的数据时,首先查询cache缓存中是否有数据以及数据是否过期,如果数据未过期则从cache读出数据,处理器会定期回写cache中的数据到内存。根据程序的局部性原理,使用cache后可以大大加快处理器访问内存数据的速度。

TLB的作秀是在处理器访问内存数据的时候做地址转换。TLB的全称是Translation

Lookaside Buffer,可以翻译做旁路缓冲。TLB中存放了一些页表文件,文件中记录了虚拟地址和物理地址的映射关系。当应用程序访问一个虚拟地址的时候,会从TLB中查询出对就的物理地址,然后访问物理地址。TLB通常是一个分层结构,使用与cache类似的原理。处理器使用一定的算法把最常用的页表放在最先访问的层次。

MMU是内存管理单元(Memory Management

Unit)的缩写,在现代计算机体系结构上,MMU被广泛应用。使用MMU技术可以向应用程序提供一个巨大的虚拟地址空间。在U-Boot初始化的时候,程序看到的地址都是物理地址,无须使用MMU。

④lowlevel_init标号

lowlevel_init标号,执行与开发板相关的初始化配置。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

.globl lowlevel_init

lowlevel_init:

ldr r0,

=SMRDATA //读取SMRDATA变量地址

ldr

r1,

_TEXT_BASE //读取_TEXT_BASE变量地址

sub

r0, r0, r1

ldr

r1,

=BWSCON //读取总线宽度寄存器

add r2, r0,

#13*4 //得到SMRDATA占用的大小

0:

ldr r3, [r0],

#4 //加载SMRDATA到内存

str r3, [r1], #4

cmp r2, r0

bne 0b

mov

pc, lr

.ltorg

SMRDATA: //定义SMRDATA的值

.word

(0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))

.word

((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))

.word

((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))

.word

((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))

.word

((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))

.word

((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))

.word

((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))

.word

((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))

.word

((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))

.word

((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)

.word

0x32

.word

0x30

.word

0x30

程序中需要计算SMRDATA需要加载的内存地址和大小。

首先读取SMRDATA的变量地址,之后计算存放的内存地址并且记录在r0寄存器,然后根据总线宽度计算需要加载的SMRDATA大小,并且把加载结束的地址存放在r2寄存器。

最后复制SMRDATA到内存。SMRDATA是开发板上内存映射的配置。

⑤relocate标号

relocate部分的代码负责把U-Boot

Stage2的代码从Flash存储器加载到内存。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

#ifndef

CONFIG_SKIP_RELOCATE_UBOOT

relocate:

adr

r0,

_start

//获取当前代码存放地址

ldr

r1,

_TEXT_BASE

//获取内存存放代码地址

cmp r0,

r1

//检查是否需要加载

beq stack_setup

ldr

r2,

_armboot_start //获取stage2代码存放地址

ldr

r3,

_bss_start //获取内存代码段起始地址

sub

r2, r3,

r2 //计算stage2代码长度

add

r2, r0,

r2 //计算stage2代码结束地址

copy_loop:

ldmia r0!,

{r3-r10}

//从Flash复制代码到内存

stmia r1!,

{r3-r10}

cmp

r0,

r2

ble

copy_loop

#endif

stack_setup: //在内存中建立堆栈

ldr

r0,

_TEXT_BASE

sub

r0, r0,

#CFG_MALLOC_LEN //分配内存区域

sub

r0, r0, #CFG_GBL_DATA_SIZE

#ifdef CONFIG_USE_IRQ

sub

r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

#endif

sub

sp, r0,

#12

clear_bss: //初始化内存bss段内容为0

ldr

r0,

_bss_start

//查找bss段起始地址

ldr

r1,

_bss_end

//查找bss段结束地址

mov r2,

#0x00000000

clbss_l:str r2,

[r0]

add

r0, r0, #4

cmp

r0, r1

ble

clbss_l

#if 0

ldr r0, =pWTCON

mov r1, #0x0

str r1, [r0]

mov

r1, #0xffffffff

ldr

r0, =INTMR

str

r1, [r0]

ldr

r0, =CLKDIVN

mov

r1, #3

str

r1, [r0]

#endif

ldr

pc,

_start_armboot //设置程序指针为start_armboot()函数地址

_start_armboot: .word

start_armboot

程序首先检查当前是否在内存中执行代码,根据结果决定是否需要从Flash存储器加载代码。

程序通过获取_start和_TEXT_BASE所在的地址比较,如果地址相同说明程序已经在内存中,无须加载。

然后计算要加载的stage2代码起始地址和长度,然后在循环复制Flash的数据到内存,每次可以复制8个字长的数据。stage2程序复制完成后,程序设置系统堆栈,最后清空内存bss段内容。

relocate程序最后在设置程序指针寄存器为start_armboot()函数地址,程序跳转到stage2部分执行,注意最后的定义,_start_armboot全局变量的值是C语言函数start_armboot()函数的地址,使用这种方式可以在汇编中调用C语言编写的函数。

另外,有一种NOR类型Flash存储器,可以像使用内存一样直接执行程序,NOR

Flash被映射到地址0开始的内存空间。

注意,程序中第12行的_armboot_start即标号⑥_armboot_start

⑦start_armboot()函数

start_armboot()函数主要初始化ARM系统的硬件和环境变量,包括Flash存储器、FrameBuffer、网卡等,最后进入U-Boot应用程序主循环。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

void start_armboot (void)

{

init_fnc_t

**init_fnc_ptr;

char *s;

#ifndef CFG_NO_FLASH

ulong

size;

#endif

#if defined(CONFIG_VFD) ||

defined(CONFIG_LCD)

unsigned long addr;

#endif

gd

= (gd_t*)(_armboot_start - CFG_MALLOC_LEN

- sizeof(gd_t));

__asm__

__volatile__("": : :"memory");

memset ((void*)gd,

0, sizeof (gd_t));

gd->bd

= (bd_t*)((char*)gd - sizeof(bd_t));

memset (gd->bd,

0, sizeof (bd_t));

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

();

}

}

#ifndef CFG_NO_FLASH

size

= flash_init

(); //初始化Flash存储器配置

display_flash_config

(size); //显示Flash存储器配置

#endif

#ifdef CONFIG_VFD

# ifndef PAGE_SIZE

# define PAGE_SIZE 4096

# endif

addr

= (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE -

1); //计算FrameBuffer内存地址

size

= vfd_setmem

(addr); //计算FrameBuffer占用内存大小

gd->fb_base

=

addr; //设置FrameBuffer内存起始地址

#endif

#ifdef CONFIG_LCD

# ifndef PAGE_SIZE

# define PAGE_SIZE 4096

# endif

addr

= (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE -

1); //计算rameBuffer内存地址

size

= lcd_setmem

(addr); //计算FrameBuffer占用内存大小

gd->fb_base

=

addr; //设置FrameBuffer内存起始地址

#endif

mem_malloc_init

(_armboot_start - CFG_MALLOC_LEN);

#if (CONFIG_COMMANDS &

CFG_CMD_NAND)

puts ("NAND: ");

nand_init(); //初始化NAND

Flash存储器

#endif

#ifdef

CONFIG_HAS_DATAFLASH

AT91F_DataflashInit(); //初始化Hash表

dataflash_print_info();

#endif

env_relocate

(); //重新设置环境变量

#ifdef CONFIG_VFD

drv_vfd_init(); //初始化虚拟显示设置

#endif

gd->bd->bi_ip_addr

= getenv_IPaddr

("ipaddr"); //设置网卡的IP地址

{

int i;

ulong

reg;

char *s,

*e;

char tmp[64];

i

= getenv_r ("ethaddr",

tmp, sizeof (tmp)); //从网卡寄存器读取MAC地址

s

= (i > 0) ? tmp : NULL;

for (reg

= 0; reg < 6; ++reg) {

gd->bd->bi_enetaddr[reg]

= s ? simple_strtoul (s, &e, 16) : 0;

if (s)

s

= (*e) ? e + 1 : e;

}

#ifdef

CONFIG_HAS_ETH1

i

= getenv_r ("eth1addr",

tmp, sizeof (tmp)); //读取Hash值

s

= (i > 0) ? tmp : NULL;

for (reg

= 0; reg < 6; ++reg) {

gd->bd->bi_enet1addr[reg]

= s ? simple_strtoul (s, &e, 16) : 0;

if (s)

s

= (*e) ? e + 1 : e;

}

#endif

}

devices_init

(); //初始化开发板上的设备

#ifdef CONFIG_CMC_PU2

load_sernum_ethaddr

();

#endif

jumptable_init

(); //初始化跳转表

console_init_r

(); //初始化控制台

#if

defined(CONFIG_MISC_INIT_R)

misc_init_r

(); //初始化其他设备

#endif

enable_interrupts

(); //打开中断

#ifdef

CONFIG_DRIVER_CS8900

cs8900_get_enetaddr

(gd->bd->bi_enetaddr); //获取CS8900网卡MAC地址

#endif

#if

defined(CONFIG_DRIVER_SMC91111) || defined

(CONFIG_DRIVER_LAN91C96)

if (getenv ("ethaddr"))

{

smc_set_mac_addr(gd->bd->bi_enetaddr); //设置SMC网卡MAC地址

}

#endif

if ((s

= getenv ("loadaddr")) != NULL)

{

load_addr

= simple_strtoul (s, NULL, 16);

}

#if (CONFIG_COMMANDS &

CFG_CMD_NET)

if ((s

= getenv ("bootfile")) != NULL)

{

copy_filename

(BootFile,

s, sizeof (BootFile)); //保存FrameBuffer

}

#endif

#ifdef

BOARD_LATE_INIT

board_late_init

(); //开发板相关设备初始化

#endif

#if (CONFIG_COMMANDS &

CFG_CMD_NET)

#if

defined(CONFIG_NET_MULTI)

puts ("Net: ");

#endif

eth_initialize(gd->bd);

#endif

for (;;)

{

main_loop

(); //进入主循环

}

}

void hang

(void)

{

puts ("###

ERROR ### Please RESET the board ###\n");

for (;;);

}

start_armboot()函数代码里有许多的宏相关,这个根据开发板的情况进行配置。函数里面的board_late_init()函数,该函数是开发板提供的,供不同的开发板做一些特有的初始化工作。

在start_armboot()函数中,使用宏开关括起来的代码是在各种开发板是最常用的功能,如CS8900网卡配置。整个函数配置完毕后,进入一个for死循环,调用main_loop()函数。这里需要注意,在main_loop()函数中也有一个for死循环。

start_armboot()函数使用死循环调用main_loop()函数,作用是防止main_loop()函数开始的初始化代码如果调用失败后重新执行初始化操作,保证程序能进入到U-Boot的命令行。

⑧main_loop()函数

main_loop()函数做的都是与具体平台无关的工作,主要包括初始化启动次数限制机制、设置软件版本号、打印启动信息、解析命令等。

❶设置启动次数有关参数。在进入main_loop()函数后,首先是根据配置加载已经保留的启动次数,并且根据配置判断是否超过启动次数。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

void main_loop (void)

{

#ifndef

CFG_HUSH_PARSER

static char lastcommand[CFG_CBSIZE]

= { 0, };

int len;

int rc

= 1;

int flag;

#endif

#if

defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >=

0)

char *s;

int bootdelay;

#endif

#ifdef CONFIG_PREBOOT

char *p;

#endif

#ifdef

CONFIG_BOOTCOUNT_LIMIT

unsigned long bootcount

= 0;

unsigned long bootlimit

= 0;

char *bcs;

char bcs_set[16];

#endif

#if defined(CONFIG_VFD)

&& defined(VFD_TEST_LOGO)

ulong

bmp =

0;

extern int trab_vfd

(ulong bitmap);

#ifdef

CONFIG_MODEM_SUPPORT

if (do_mdm_init)

bmp

=

1;

#endif

trab_vfd

(bmp);

#endif

#ifdef

CONFIG_BOOTCOUNT_LIMIT

bootcount

=

bootcount_load(); //加载保存的启动次数

bootcount++; //启动次数加1

bootcount_store

(bootcount); //更新启动次数

sprintf (bcs_set, "%lu",

bootcount); //打印启动次数

setenv

("bootcount", bcs_set);

bcs

= getenv ("bootlimit");

bootlimit

= bcs ? simple_strtoul (bcs, NULL, 10) :

0; //转换启动次数字符串为UINT类型

#endif

函数启动次数限制功能,启动次数限制可以被用户设置一个启动次数,然后保存在Flash存储器的特定位置,当到达启动次数后,U-Boot无法启动,该功能适合一些商业产品,通过配置不同的License限制用户重新启动系统。

❷接下来是Modem功能。如果系统中有Modem,打开该功能可以接受其他用户通过电话网络的拨号请求。Modem功能通常供一些远程控制的系统使用

1

2

3

4

5

6

7

8

9

10

#ifdef

CONFIG_MODEM_SUPPORT

debug

("DEBUG: main_loop: do_mdm_init=%d\n", do_mdm_init);

if (do_mdm_init)

{ //判断是否需要初始化Modem

char *str

=

strdup(getenv("mdm_cmd")); //获取Modem参数

setenv

("preboot", str);

if (str

!= NULL)

free (str);

mdm_init(); //初始化Modem

}

#endif

❸然后设置U-Boot版本号,初始化命令自动完成功能等。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

#ifdef

CONFIG_VERSION_VARIABLE

{

extern char version_string[];

setenv

("ver",

version_string); //设置版本号

}

#endif

#ifdef

CFG_HUSH_PARSER

u_boot_hush_start

(); //初始化Hash功能

#endif

#ifdef

CONFIG_AUTO_COMPLETE

install_auto_complete(); //初始化命令自动完成功能

#endif

#ifdef CONFIG_PREBOOT

if ((p

= getenv ("preboot")) != NULL)

{

# ifdef

CONFIG_AUTOBOOT_KEYED

int prev

=

disable_ctrlc(1);

//关闭Crtl+C组合键

# endif

# ifndef

CFG_HUSH_PARSER

run_command

(p, 0); //运行Boot参数

# else

parse_string_outer(p,

FLAG_PARSE_SEMICOLON |

FLAG_EXIT_FROM_LOOP);

# endif

# ifdef

CONFIG_AUTOBOOT_KEYED

disable_ctrlc(prev);

//恢复Ctrl+C组合键

# endif

}

#endif

程序开始是动态版本号功能支持代码,version_string变量是在其他文件定义的一个字符串变量,当用户改变U-Boot版本的时候会更新该变量。打开动态版本支持功能后,U-Boot在启动的时候会显示最新的版本号。

install_auto_comlpete()函数设置命令行自动完成功能,该功能与linux的shell类似,当用户输入一个部分命令后,可以通过按下键盘上的Tab键补全命令的剩余部分,main_loop()函数不同的功能使用宏开关控制不仅能提高代码模块化,理主要的是针对嵌入式系统Flash存储器大小设计的。在嵌入式系统上,不同的系统Flash存储空间不同。对于一些Flash空间比较紧张的设备来说,通过宏开关关闭一些不是特别必要的功能如命令行自动完成,可以减小U-Boot编译后的文件大小。

❹在进入主循环之前,如果配置了启动延迟功能,需要等待用户从串口或者网络接口输入。如果用户按下任意键打断,启动流程,会向终端打印出一个启动菜单。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

#if

defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >=

0)

s

= getenv ("bootdelay");

bootdelay

= s ? (int)simple_strtol(s, NULL, 10) :

CONFIG_BOOTDELAY; //启动延迟

debug

("### main_loop entered: bootdelay=%d\n\n", bootdelay);

# ifdef

CONFIG_BOOT_RETRY_TIME

init_cmd_timeout

(); //初始化命令行超时机制

# endif

#ifdef

CONFIG_BOOTCOUNT_LIMIT

if (bootlimit

&& (bootcount > bootlimit))

{ //检查是否超出启动次数限制

printf ("Warning:

Bootlimit (%u) exceeded. Using altbootcmd.\n",

(unsigned)bootlimit);

s

= getenv ("altbootcmd");

}

else

#endif

s

= getenv ("bootcmd"); //获取启动命令参数

debug

("### main_loop: bootcmd=\"%s\"\n", s ? s

: "");

if (bootdelay

>= 0 && s && !abortboot (bootdelay))

{ //检查是否支持启动延迟功能

# ifdef

CONFIG_AUTOBOOT_KEYED

int prev

=

disable_ctrlc(1);

//关闭Ctrl+C组合键

# endif

# ifndef

CFG_HUSH_PARSER

run_command

(s, 0); //运行启动命令行

# else

parse_string_outer(s,

FLAG_PARSE_SEMICOLON |

FLAG_EXIT_FROM_LOOP);

# endif

# ifdef

CONFIG_AUTOBOOT_KEYED

disable_ctrlc(prev);

//打开Ctrl+C组合键

# endif

}

# ifdef

CONFIG_MENUKEY

if (menukey

== CONFIG_MENUKEY)

{ //检查是否支持菜单键

s

= getenv("menucmd");

if (s)

{

# ifndef

CFG_HUSH_PARSER

run_command

(s, 0);

# else

parse_string_outer(s,

FLAG_PARSE_SEMICOLON |

FLAG_EXIT_FROM_LOOP);

# endif

}

}

#endif

#endif

#ifdef

CONFIG_AMIGAONEG3SE

{

extern void video_banner(void);

video_banner(); //打印启动图标

}

#endif

❺在各功能设置完毕后,程序进入一个for死循环,该循环不断使用readline()函数从控制台(一般是串口)读取用户的输入,然后解析,有关如何解析命令则可以参考U-Boot代码中run_command()函数的定义。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

#ifdef

CFG_HUSH_PARSER

parse_file_outer();

for (;;);

#else

for (;;)

{ //进入命令行超时

#ifdef

CONFIG_BOOT_RETRY_TIME

if (rc

>= 0) {

reset_cmd_timeout(); //设置命令行超时

}

#endif

len

= readline

(CFG_PROMPT); //读取命令

flag

= 0;

if (len

> 0)

strcpy (lastcommand,

console_buffer);

else if (len

== 0)

flag

|= CMD_FLAG_REPEAT;

#ifdef

CONFIG_BOOT_RETRY_TIME

else if (len

== -2) {

puts ("\nTimed

out waiting for command\n");

# ifdef

CONFIG_RESET_TO_RETRY

do_reset

(NULL, 0, 0, NULL);

# else

return;

# endif

}

#endif

if (len

== -1)

puts ("\n");

else

rc

= run_command (lastcommand,

flag); //运行命令

if (rc

<= 0) {

lastcommand[0]

= 0;

}

}

#endif

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值