linux怎么编译vboot,vboot完全解读

上半个月在学习bootloader,突然找到了一个非常好的vboot,vboot只有最基本的内核引导功能(基于s3c2440,从nand flash启动),对其深入研究后,发现对bootloader有了比较全面的理解,虽然没有像uboot那么多功能,但vboot已经实现了bootloader最核心的功能,其他像什么网络功能、烧写功能等等也只是一些裸机驱动而已。学习bootloader需要有汇编的基础,如果有单片机编程经验的话那更是“如鱼得水”了。

先看vboot的整体架构,下面是vboot包含的所有文件:

134125261_1_20180527095929691.png

很简单是吧,其中核心的文件是head.S、main.c和nand.c,vboot.bin已经是编译出来的二进制文件,用于烧写在nand flash里。先看mem.lds文件,这是一个链接脚本,从那里可以找到程序的入口:

134125261_2_20180527095929832.gif

1 SECTIONS {

2 . = 000000;

3 .myhead ALIGN(0): {*(.text.FirstSector)}

4 .text ALIGN(512): { *(.text) }

5 .bss ALIGN(4) : { *(.bss*) *(COMMON) }

6 .data ALIGN(4) : { *(.data*) *(.rodata*) }

7 }

134125261_2_20180527095929832.gif

比较简单,程序入口位于text.FirstSector这个段里(因为程序是从nand flash的0地址开始执行的),它在head.S文件里定义:

134125261_2_20180527095929832.gif

1 .section .text.FirstSector

2 .globl first_sector

3

4 first_sector:

5 @ 0x00: Reset

6 b Reset

7

8 @ 0x04: Undefined instruction exception

9 UndefEntryPoint:

10 b UndefEntryPoint

11

12 @ 0x08: Software interrupt exception

13 SWIEntryPoint:

14 b SWIEntryPoint

15

16 @ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort)

17 PrefetchAbortEnteryPoint:

18 b PrefetchAbortEnteryPoint

19

20 @ 0x10: Data Access Memory Abort

21 DataAbortEntryPoint:

22 b DataAbortEntryPoint

23

24 @ 0x14: Not used

25 NotUsedEntryPoint:

26 b NotUsedEntryPoint

27

28 @ 0x18: IRQ(Interrupt Request) exception

29 IRQEntryPoint:

30 b IRQHandle

31

32 @ 0x1c: FIQ(Fast Interrupt Request) exception

33 FIQEntryPoint:

34 b FIQEntryPoint

35

36 @0x20: Fixed address global value. will be replaced by downloader.

37

38 .long ZBOOT_MAGIC

39 .byte OS_TYPE, HAS_NAND_BIOS, (LOGO_POS & 0xFF), ((LOGO_POS >>8) &0xFF)

40 .long OS_START

41 .long OS_LENGTH

42 .long OS_RAM_START

43 .string LINUX_CMD_LINE

134125261_2_20180527095929832.gif

第5~34行的作用是安装异常向量表,在这里除了复位,其他异常都没有定义具体的执行代码。

134125261_2_20180527095929832.gif

1 .section .text

2 Reset:

3 @ 关闭看门狗

4 mov r1, #0x53000000

5 mov r2, #0x0

6 str r2, [r1]

7

8 @ 关闭中断

9 mov r1, #INT_CTL_BASE

10 mov r2, #0xffffffff

11 str r2, [r1, #oINTMSK]

12 ldr r2, =0x7ff

13 str r2, [r1, #oINTSUBMSK]

14

15 @ 初始化系统时钟

16 mov r1, #CLK_CTL_BASE

17 mvn r2, #0xff000000

18 str r2, [r1, #oLOCKTIME] @设置LOCKTIME寄存器

19

20 mov r1, #CLK_CTL_BASE

21 ldr r2, clkdivn_value

22 str r2, [r1, #oCLKDIVN] @设置分频寄存器

23

24 mrc p15, 0, r1, c1, c0, 0 @ read ctrl register

25 orr r1, r1, #0xc0000000 @ Asynchronous 异步总线模式

26 mcr p15, 0, r1, c1, c0, 0 @ write ctrl register

27

28 mov r1, #CLK_CTL_BASE

29 ldr r2, =S3C2440_UPLL_48MHZ_Fin12MHz

30 str r2, [r1, #oUPLLCON]

31

32 nop

33 nop

34 nop

35 nop

36 nop

37 nop

38 nop

39 nop

40 nop

41

42 ldr sp, DW_STACK_START @ setup stack pointer

43

44 ldr r2, mpll_value_USER @ clock user set 12MHz

45 str r2, [r1, #oMPLLCON]

46 bl memsetup

47

48 @ set GPIO for UART

49 mov r1, #GPIO_CTL_BASE

50 add r1, r1, #oGPIO_H

51 ldr r2, gpio_con_uart

52 str r2, [r1, #oGPIO_CON]

53 ldr r2, gpio_up_uart

54 str r2, [r1, #oGPIO_UP]

55 bl InitUART

56

57

58 @ get read to call C functions

59 mov fp, #0 @ no previous frame, so fp=0

60 mov a2, #0 @ set argv to NULL

61

62 bl Main

63

64 1: b 1b @

134125261_2_20180527095929832.gif

第4~6行,关闭看门狗,以免系统不断复位;第9~13行,关闭中断;第16~18行,设置系统时钟稳定(锁定)时间;第20~22行,设置时钟分频比为1:4:8(FCLK:HCLK:PCLK);第24~26行,设置为异步总线模式(因为FCLK已经不等于HCLK);第28~30,行,设置UPLL为48MHZ,用于USB通信;第42行,设置栈指针,为下面调用c程序做准备;第44~45行,设置FCLK为400MHZ,那么HCLK=100MHZ,PCLK=50MHZ;第46行,跳到内存初始化程序:

134125261_2_20180527095929832.gif

1 memsetup:

2 @ initialise the static memory

3

4 @ set memory control registers

5 mov r1, #MEM_CTL_BASE

6 adrl r2, mem_cfg_val

7 add r3, r1, #52 @13*4

8 1: ldr r4, [r2], #4

9 str r4, [r1], #4

10 cmp r1, r3

11 bne 1b

12 mov pc, lr

134125261_2_20180527095929832.gif

2440总共有13个设置内存的寄存器,因此第7行的立即数是52(13*4);第8~11行,通过循环设置13个寄存器的值。返回到memsetup下面的代码:

134125261_2_20180527095929832.gif

1 @ set GPIO for UART

2 mov r1, #GPIO_CTL_BASE

3 add r1, r1, #oGPIO_H

4 ldr r2, gpio_con_uart

5 str r2, [r1, #oGPIO_CON]

6 ldr r2, gpio_up_uart

7 str r2, [r1, #oGPIO_UP]

8 bl InitUART

9

10

11 @ get read to call C functions

12 mov fp, #0 @ no previous frame, so fp=0

13 mov a2, #0 @ set argv to NULL

14

15 bl Main

16

17 1: b 1b @

134125261_2_20180527095929832.gif

第2~8行,用于初始化串口(115200bps,8N1);第12~13行,设置两个arm寄存器;第15行,跳到Main函数执行。在main.c文件里:

134125261_2_20180527095929832.gif

1 void Main(void)

2 {

3 MMU_EnableICache();

4 MMU_EnableDCache();

5

6 Port_Init();

7 NandInit();

8

9 if (g_page_type == PAGE_UNKNOWN) {

10 Uart_SendString("\r\nunsupport NAND\r\n");

11 for(;;);

12 }

13

14 GetParameters();

15

16 Uart_SendString("loading Image of Linux from Nand Flash...\n\r");

17 ReadImageFromNand();

18 }

134125261_2_20180527095929832.gif

第3~4行,使能Dcache和Icache:

134125261_2_20180527095929832.gif

static inline void MMU_EnableICache(void)

{

asm (

"mrc p15,0,r0,c1,c0,0\n"

"orr r0,r0,#(1<<12)\n"

"mcr p15,0,r0,c1,c0,0\n"

);

}

static inline void MMU_EnableDCache(void)

{

asm (

"mrc p15,0,r0,c1,c0,0\n"

"orr r0,r0,#(1<<2)\n"

"mcr p15,0,r0,c1,c0,0\n"

);

}

134125261_2_20180527095929832.gif

第6行,初始化一些IO口(没用到);第7行,初始化nand flash控制器,在nand.c文件里定义:

134125261_2_20180527095929832.gif

void NandInit(void)

{

NFCONF = (TACLS << 12) | (TWRPH0 << 8) | (TWRPH1 << 4) | (0 << 0);

NFCONT =

(0 << 13) | (0 << 12) | (0 << 10) | (0 << 9) | (0 << 8) | (0 << 6) |

(0 << 5) | (1 << 4) | (1 << 1) | (1 << 0);

NFSTAT = 0;

NandReset();

NandCheckId();

}

134125261_2_20180527095929832.gif

设置具体nand flash芯片的时序参数、页的大小和位宽等,初始化之后,就可以读写nand flash了。回到Main函数的第14行调用的GetParameters()函数的定义:

134125261_2_20180527095929832.gif

static inline void GetParameters(void)

{

U32 Buf[2048];

g_os_type = OS_LINUX;

//内核在flash中的起始地址

g_os_start = 0x50000;

//内核映像的大小

g_os_length = 0x300000;

//内核被拷贝到内存的起始地址

g_os_ram_start = 0x30008000;

// vivi LINUX CMD LINE

//从flash的参数分区中读命令行参数

NandReadOneSector((U8 *)Buf, 0x40000);

if (Buf[0] == 0x49564956 && Buf[1] == 0x4C444D43) {

memcpy(g_linux_cmd_line, (char *)&(Buf[2]), sizeof g_linux_cmd_line);

}

}

134125261_2_20180527095929832.gif

设置了内核映像在nand flash的起始地址和大小,还有设置内核映像被拷贝到ram的起始地址,命令行参数是通过BIOS(nor flash里的supervivi)写到nand flash的0x40000地址处,通过NandReadOneSector()把它读出来,其中Buf[0]、Buf[1]这两个值是“暗藏值”,是对应于具体的BIOS的,是由BIOS写进去的,位于命令行参数的第一和第二个字,因为BIOS的代码不不开源的,无法修改,所以移植vboot的时候只要是用这个BIOS来烧写vboot就不用修改两个值(不用太纠结,我曾纠结了很久)。从memcpy()函数也可以知道,Buf[0]和Buf[1]这两个值是用来识别具体的BIOS的,没用于命令行参数。现在看NandReadOneSector()函数:

134125261_2_20180527095929832.gif

1 int NandReadOneSector(U8 * buffer, U32 addr)

2 {

3 int ret;

4

5 switch(g_page_type) {

6 case PAGE512:

7 ret = NandReadOneSectorP512(buffer, addr);

8 break;

9 case PAGE2048:

10 ret = NandReadOneSectorP2048(buffer, addr);

11 break;

12 default:

13 for(;;);

14 }

15 return ret;

16 }

134125261_2_20180527095929832.gif

因为我板子(GT2440)上的nand flash是64M的,页的大小为512字节,所以看第7行的调用:

134125261_2_20180527095929832.gif

static inline int NandReadOneSectorP512(U8 * buffer, U32 addr)

{

U32 sector;

sector = addr >> 9;

NandReset();

#if 0

NF_RSTECC();

NF_MECC_UnLock();

#endif

NF_nFCE_L();

NF_CLEAR_RB();

NF_CMD(0x00);

NF_ADDR(0x00);

NF_ADDR(sector & 0xff);

NF_ADDR((sector >> 8) & 0xff);

NF_ADDR((sector >> 16) & 0xff);

delay();

NF_DETECT_RB();

ReadPage512(buffer, &NFDATA);

#if 0

NF_MECC_Lock();

#endif

NF_nFCE_H();

return 1;

}

134125261_2_20180527095929832.gif

该函数里前面那些是设置读操作,设置读起始地址,核心是调用ReadPage512()函数,它由汇编实现,在head.S里:

134125261_2_20180527095929832.gif

1 .globl ReadPage512

2

3 ReadPage512:

4 stmfd sp!, {r2-r7} @ 将r2~r7寄存器的值压栈

5 mov r2, #0x200 @ 512个字节

6

7 1:

8 ldr r4, [r1]

9 ldr r5, [r1]

10 ldr r6, [r1]

11 ldr r7, [r1]

12 stmia r0!, {r4-r7}

13 ldr r4, [r1]

14 ldr r5, [r1]

15 ldr r6, [r1]

16 ldr r7, [r1]

17 stmia r0!, {r4-r7}

18 ldr r4, [r1]

19 ldr r5, [r1]

20 ldr r6, [r1]

21 ldr r7, [r1]

22 stmia r0!, {r4-r7}

23 ldr r4, [r1]

24 ldr r5, [r1]

25 ldr r6, [r1]

26 ldr r7, [r1]

27 stmia r0!, {r4-r7}

28 subs r2, r2, #64 @ 一次循环读64个字节

29 bne 1b;

30 ldmfd sp!, {r2-r7} @ 恢复r2~r7寄存器的值

31 mov pc,lr @ 返回

134125261_2_20180527095929832.gif

挺好懂的,不多解析。再回到Main()函数的17行(最后一个函数调用)调用ReadImageFromNand():

134125261_2_20180527095929832.gif

1 void ReadImageFromNand(void)

2 {

3 unsigned int Length;

4 U8 *RAM;

5 unsigned BlockNum;

6 unsigned pos;

7

8 Length = g_os_length;

9 //内核的大小(单位:块)

10 Length = (Length + BLOCK_SIZE - 1) >> (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT) << (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT); // align to Block Size

11 //内核在flash中的第几块

12 BlockNum = g_os_start >> (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT);

13 //要拷贝到的起始地址

14 RAM = (U8 *) g_os_ram_start;

15 for (pos = 0; pos < Length; pos += BLOCK_SIZE) {

16 unsigned int i;

17 // skip badblock

18 //坏块检测

19 for (;;) {

20 if (NandIsGoodBlock

21 (BlockNum <<

22 (BYTE_SECTOR_SHIFT + SECTOR_BLOCK_SHIFT))) {

23 break;

24 }

25 BlockNum++; //try next

26 }

27 for (i = 0; i < BLOCK_SIZE; i += SECTOR_SIZE) {

28 int ret =

29 NandReadOneSector(RAM,

30 (BlockNum <<

31 (BYTE_SECTOR_SHIFT +

32 SECTOR_BLOCK_SHIFT)) + i);

33 RAM += SECTOR_SIZE;

34 ret = 0;

35

36 }

37

38 BlockNum++;

39 }

40

41 CallLinux();

42 }

134125261_2_20180527095929832.gif

主要是从nand flash里把内核映像一块一块地读到ram里,每读一块之前先进行坏块检测,如果是坏块就跳过,继续读下一块(这里的坏块检测是一个比较粗略的检测方法),直到把整个内核映像读到ram里面。这里内核映像的大小设置为3M(实际上不到3M),因此读也是读3M大小到ram里面。最后该函数的第41行调用CallLinux():

134125261_2_20180527095929832.gif

1 static void CallLinux(void)

2 {

3 struct param_struct {

4 union {

5 struct {

6 unsigned long page_size; /* 0 */

7 unsigned long nr_pages; /* 4 */

8 unsigned long ramdisk_size; /* 8 */

9 unsigned long flags; /* 12 */

10 unsigned long rootdev; /* 16 */

11 unsigned long video_num_cols; /* 20 */

12 unsigned long video_num_rows; /* 24 */

13 unsigned long video_x; /* 28 */

14 unsigned long video_y; /* 32 */

15 unsigned long memc_control_reg; /* 36 */

16 unsigned char sounddefault; /* 40 */

17 unsigned char adfsdrives; /* 41 */

18 unsigned char bytes_per_char_h; /* 42 */

19 unsigned char bytes_per_char_v; /* 43 */

20 unsigned long pages_in_bank[4]; /* 44 */

21 unsigned long pages_in_vram; /* 60 */

22 unsigned long initrd_start; /* 64 */

23 unsigned long initrd_size; /* 68 */

24 unsigned long rd_start; /* 72 */

25 unsigned long system_rev; /* 76 */

26 unsigned long system_serial_low; /* 80 */

27 unsigned long system_serial_high; /* 84 */

28 unsigned long mem_fclk_21285; /* 88 */

29 } s;

30 char unused[256];

31 } u1;

32 union {

33 char paths[8][128];

34 struct {

35 unsigned long magic;

36 char n[1024 - sizeof(unsigned long)];

37 } s;

38 } u2;

39 char commandline[1024];

40 };

41 //启动参数在内存的起始地址

42 struct param_struct *p = (struct param_struct *)0x30000100;

43 memset(p, 0, sizeof(*p));

44 memcpy(p->commandline, g_linux_cmd_line, sizeof(g_linux_cmd_line));

45 //内存页的大小4K

46 p->u1.s.page_size = 4 * 1024;

47 //内存总共有多少页

48 p->u1.s.nr_pages = 64 * 1024 * 1024 / (4 * 1024);

49

50 {

51 unsigned int *pp = (unsigned int *)(0x30008024);

52 if (pp[0] == 0x016f2818) { //zImage的魔数,在内核中定义

53 //Uart_SendString("\n\rOk\n\r");

54 } else {

55 Uart_SendString("\n\rWrong Linux Kernel\n\r");

56 for (;;) ;

57 }

58

59 }

60 asm (

61 "mov r5, %2\n"

62 "mov r0, %0\n"

63 "mov r1, %1\n"

64 "mov ip, #0\n"

65 "mov pc, r5\n"

66 "nop\n" "nop\n": /* no outpus */

67 :"r"(0), "r"(782), "r"(g_os_ram_start)

68 );

69 }

134125261_2_20180527095929832.gif

首先定义了一个struct param_struct结构体变量,从这里就可以看出,vboot用的是旧的方式(新的是用tag方式),struct param_struct与内核里定义的一样。第41~59行,看注释可以明白,第60~67行,是内核的一些约定:

R0 = 0

R1 = 机器ID

.....

最后第65行,设置pc为内核映像在内存中的起始地址,直接跳到内核映像的入口,从而开始内核代码的执行......

总结:

vboot是一个十分精简的bootloader,从nand flash启动,目前只支持2440 Linux,只有引导内核的功能,它的编译后的二进制文件不会超过4K(这是由2440从nand flash启动所限制的),编译vboot只需要在代码目录下执行make,便可生成vboot.bin文件,通过BIOS将它烧写到nand flash里。强烈推荐想学习ARM bootloader的同学从vboot开始入手。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
我用Vboot工具安装VHD XP的方法(过期就会0x0000012f蓝屏) 经过好多次的蓝屏和无显示等各种故障,现在成功了。 一法: 1、运行“WinXP VHD 辅助处理工具 2011 【11-02-22最后更新】”,替换C:\vboot_temp以下文件: ①: VBOOT.IMG(I386下已换无时间限制的VBOOTDSK.SYS,若有$oem$/textmode/VBOOTDSK.SYS也要换没有不管) ②: floppies\vboot-img\i386\VBOOTDSK.SYS ③: tools\x86\drivers\vbootdsk\VBOOTDSK.SYS 若不换会0x0000012f蓝屏的。 2、选安装版XPISO文件或者NTFS格式的GHOST XP。重启。 我的笔记本这样装成功了。 台式机BIOS改SATA Mode为combiled,没有IDE选项,启动找不到硬盘,改其它都不行。只有用下面的方法了。 二法: 1、如以前安装GHOST XP一样,启动运行GHOST在实机C盘上安装,把所有驱动、设置、自己要用的程序装完。若安装版XP一样,不忘了NTFS格式。 2、重启运行GHOST备份C盘到其它盘WINXP.GHO。 3、重启进入C盘装好的XP,运行WinXP VHD辅助处理工具,如最上面第一步。 4、重启选VHD XP进入Vboot菜单选第一项继续安装,这样不会有各种故障了。 5、后面的都知道的,实C盘上XP可以不要了,须留下boot.ini、bootfont.bin、NTDETECT.COM、ntldr、vbootldr、vbootldr.mbr、WINXP.VHD文件及vboot文件夹。 没办法,我的台式机只能用第二种方法成功,并且我安装上了5个网上下载的XP,编辑grub.cfg文件(不能有中文)出现了5个XP菜单,选择启动想用哪个都可以。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值