解决方案

BootLoader介绍

seo靠我 2023-09-25 22:51:32

文章目录

一.BootLoader的引入二.BootLoader的启动方式三.BootLoader的结构和启动过程四.自己写一个BootLoader1.BootLoader第一阶段2.BootLoadeSEO靠我r第二阶段

一.BootLoader的引入

首先我们知道对于pc机,他的启动过程是:

BIOS(启动)—>Windows内核(挂在C/D盘)—>系统盘/应用盘(启动)—>应用程序

而对于嵌入式系统(比如AndSEO靠我roid手机,工控设备等)他的启动过程是:

BootLoader(启动)—>Linux kernel(挂载)—>根文件系统(启动)—>APP

系统上电之后需要一段程序来进行初始化,比如关闭看门狗,改变系统SEO靠我时钟,初始化存储控制器,将更多的代码复制到内存中等等,这些主要是将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好环境,而这一小段程序就是BootLoader。当然为了方便开发,也SEO靠我可以增加一些功能,比如增加网络功能,这样来增强BootLoader的功能。

二.BootLoader的启动方式

大多数的BootLoader都有两种不同的操作模式:“启动加载模式”和“下载模式”。但是注意SEO靠我BootLoader的最终目的是启动内核,因此其实这两种当时并没有所谓的差别。

1.启动加载(boot loading)模式上电后,BootLoader从板子的某个固态存储设备上将操作系统加载到RAM中SEO靠我运行,整个过程没有用户介入。这种模式是BootLoader的正常工作模式,产品发布时候,BootLoader就工作在这种模式下。

2.下载(downloading)模式这种模式下,开发人员使用各种命令,SEO靠我通过串口连接或者网络连接从主机上下载文件,将他们直接放在内存运行或者烧入flash类固态存储设备中。以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot LoaSEO靠我der 通常都会向它的终端用户提供一个简单的命令行接口。

对于串口通信的方式:使用xmodem/ymodem/zmodem 协议

对于网络通信的方式:使用tftp,nfs服务等

三.BootLoader的结SEO靠我构和启动过程

1.概述

嵌入式Linux系统的4个层次

(1)引导加载程序

:BootLoader

(2)Linux内核

:特定于嵌入式板子的定制内核和内核的启动参数。内核的启动参数可以是内核默认的,也可以是BoSEO靠我otLoader传递给他的。

(3)文件系统

:包括根文件系统和建立于flash内存设备之上的文件系统。里面包含了Linux系统能够运行所必须的应用程序、库等,比如可以给用户提供操作Linux界面的sheSEO靠我ll程序、动态链接的程序运行时需要的glibc等。

(4)用户应用程序:特定用户的应用程序。

嵌入式Linux系统的典型分区结构:

boot parameters:分区中存放一些可以设置的参数,如:IP地址SEO靠我,串口波特率、要传递给内核的命令行参数等。正常启动过程中,BootLoader先运行,然后他将内核复制到内存中,并且在内核某个固定位置设置好要传递给内核的参数,最后运行内核,内核启动之后,他会挂在根文SEO靠我件系统(root filesystem),启动文件系统中的应用程序。

2.BootLoader的两个阶段

BootLoader的启动过程可以分为单阶段、多阶段两种,多阶段的BootLoader能提供更加复SEO靠我杂的功能以及更好的移植性。

从固态存储设备上启动的BootLoader大多都是两阶段的启动过程。第一阶段用汇编实现,完成一些依赖于CPU体系结构的初始化,并调用第二阶段的代码;第二阶段则是通常使用C语言SEO靠我来实现,这样可以实现更加复杂的功能,而且代码会有更好的可读性和可移植性。

第一阶段

1、硬件设备初始化:关闭看门狗、关中断、设置CPU的速度和时钟频率、RAM初始化等。

2、为加载BootLoader的第SEO靠我二阶段准备RAM空间

3、复制BootLoader的第二阶段代码到RAM空间中

4、设置好栈

5、跳转到第二阶段代码的C入口处

第二阶段

1、初始化本阶段要使用的硬件设备

2、检查系统内存映射(memory mSEO靠我ap):就是确定板子使用了多少内存,他们的地址空间是什么。

3、将内核映象和根文件映象从flash上读到RAM空间中

4、为内核映象设置启动参数

5、调用内核

为了开发的方便,至少要初始化一个串口以便程序员与SEO靠我BootLoader交互。

四.自己写一个BootLoader

BootLoader是裸板程序组成,因此可以参考uboot来写。

写一个简单的BootLoader,他的功能就是能够启动内核。因此,BootLSEO靠我oader所要实现的功能就是:

(1)关闭看门狗:开发板默认是打开的,如果不关闭,那么在开发板起来之后一段时间之后会复位

(2)初始化时钟:设置分频系数,为了让系统能够跑的更快

(3)初始化sdram

(4)SEO靠我重定位代码:如果BootLoader太大,那么就需要重定位,也就是将BootLoader本身的代码从flash复制到他的链接地址去

(5)执行main函数,就是执行启动的第二阶段

1.BootLoaderSEO靠我第一阶段

首先写出一个汇编文件start.s

1、关闭看门狗

/* 1、关闭看门狗 * 对于s3c2440来说看门狗默认是关闭的,如果不关闭,那么在开发板起来之后一段时间后会复位* 对于2440来说,看门狗SEO靠我的地址是0x53000000,只要将它置为0即可*//* 这是一条伪汇编指令 * 编译器在编译的时候发现指令比较复杂,会把他拆分成两条指令,先把他放到某个地址,然后再去这个地址读出来*/ldr r0,SEO靠我 =0x53000000ldr r1, =0 /* 值比较简单的话就直接用mov指令 */str r1, [r0] /* 将r1的值存放到r0所在的地址 */

2、设置时钟

设置时钟的目的就是为了让系统能SEO靠我够跑的更快,因此在这里将FCLK设置为400MHz,将HCLK设置为100MHz,将PCLK设置为50MHz。

也就是FCLK:HCLK:PCLK = 1:4:8

怎么编程控制MPLL、HDIV、PDIVSEO靠我,使FCLK=400MHz,HCLK=100MHz,PLCK=50MHz?

因此需要设置`MPLLCON`的FCLK=400MHz,设置`CLKDIVN`的HCLK=FCLK/4,PCLK=FCLK/8SEO靠我

看芯片手册得知:使用CLKDIVN寄存器来设置这些CLK

那么就可以写出一下代码:

根据PDIVN的第0位和HDVIN的第2位为1,即101,可以计算出为0x5/* 设置分频比为FCLK:HCLK:PCSEO靠我LK=1:4:8 HDIVN=2,PDIVN=1*/ldr r0, =0x4C000014ldr r1, =0x5str r1, [r0]

同时看到芯片手册notes:

这里我们设置的HDIVN=2,并不SEO靠我为0,因此就需要设置为异步模式(手册要求),CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode”

直接复制即可

mrc p15, 0, r1, c1, cSEO靠我0, 0 /* 读出控制寄存器 */ orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */mcr p15, 0, r1, c1, c0, 0SEO靠我 /* 写入控制寄存器 */

r:寄存器

c:协处理器

mrc:从协处理器读取某个值放入寄存器中

mcr:从寄存器中读取某个值到协处理器中

既然我们让HCLK = 400MHz,我们就要去设置对应的寄存器MPLSEO靠我LCON

当晶振为12MHz的时候,MDIV = 92,PDIV = 1,SDIV = 1

因此可以通过计算得到

m = 100;

p = 3;

s = 1;

Fin为12M

MPLL = (210012)/(3*SEO靠我2) = 400MHz

再来看到MPLLCON寄存器:

因此需要设置为:MPLLCON = ((92<<12) | (1<<4) | (1<<0));

代码实现为:

/* 对应的开发板为2440,设置MPLLSEO靠我CON = S3C2440_MPLL_200MHZ */ldr r0, =0x4c000004ldr r1, =S3C2440_MPLL_400MHZstr r1, [r0]/* 一旦设置PLL, 就SEO靠我会锁定lock time直到PLL输出稳定* 然后CPU工作于新的频率FCLK*/

对于S3C2440_MPLL_400MHZ的定义我们放在开头:

#define S3C2440_MPLL_400MHZ SEO靠我((0x5c<<12)|(0x01<<4)|(0x01))

3、初始化sdram2440一共有8个BANK,关于各个BANK的性质,在这里不再叙述,需要按照数据手册,将BWSCON,BANKCON0~7SEO靠我,REFRESH,BANKSIZE,MRSRB6,MRSRB7这几个寄存器的值一一算出来,然后依次存到寄存器中

sdram_config是程序的一个标号,用来存放算出来的这些值,然后用上面的方法一一存进SEO靠我

sdram_config:.long 0x22011110 //BWSCON.long 0x00000700 //BANKCON0.long 0x00000700 //BANKCON1.long 0SEO靠我x00000700 //BANKCON2.long 0x00000700 //BANKCON3 .long 0x00000700 //BANKCON4.long 0x00000700 //BANKCOSEO靠我N5.long 0x00018005 //BANKCON6.long 0x00018005 //BANKCON7.long 0x008C04F4 // REFRESH.long 0x000000B1 SEO靠我//BANKSIZE.long 0x00000030 //MRSRB6.long 0x00000030 //MRSRB7

汇编代码如下:

/*3、初始化SRAM */ldr r0, =MEM_CTL_BASEO靠我SEadr r1, sdram_config /* sdram_config的当前地址 */add r3, r0, #(13*4) 1:ldr r2, [r1], #4 /*让r1地址SEO靠我的值读到r2,让后r1加4,也就是指向下一个地址*/str r2, [r0], #4 /*让r2的值写入到r0地址的寄存器,r0加上4,指向下一个地址*/cmp r0, r3 /*不断的循环把sdraSEO靠我m_config里面的值写入到BWSCON开始的寄存器里面*/bne 1b /*b的含义代表调到这行代码前面的1,如果是1f就代表下面的1*/

对于MEM_CTL_BASE 的定义为:#define MSEO靠我EM_CTL_BASE 0x48000000

4、重定位 注意用C语言写的函数这些要先设置好栈,那么为什么汇编语言调用C函数要设置栈?

(1)保存现场:也就是寄存器的值,防止被破坏。因此,在函数调用之前,应SEO靠我该将这些寄存器等现场暂时保存(入栈push),等调用函数执行完毕后出栈(pop)再恢复现场。这样CPU就可以正确的继续执行了。

(2)传递参数:C语言函数调用时,会传给被调用函数一些参数,对于这些C语言SEO靠我级别参数,被编译器翻译成汇编语言时,要找个地方存放下来,并且让被调用函数能访问,否则没法传递。找个地方存放下来分2种情况。一是,本身传递的参数不多于4个,可以通过寄存器传送。因为在前面的保存现场动作中SEO靠我,已经保存好对应的寄存器的值,此时这些寄存器是空闲的,可以供我们使用存放参数。二是,参数多于4个,寄存器不够用,就得用栈。

(3)保存临时变量:这些临时变量包括函数的非静态局部变量以及编译器自动生成的其SEO靠我他临时变量。

对于重定位,我们为什么要进行重定位? 我们板子中有nor flash、SDRAM和nand flash,还有一个4k的片内内存SRAM。

CPU能直接访问的地方有:nor flash、SDRASEO靠我M、SRAM和各种控制器(包括NAND flash控制器)。所以当我们的程序烧写到SDRAM或者NOR flash的时候,程序能直接运行。但是如果烧写到NAND flash,芯片会把程序的头4K先拷贝SEO靠我到SRAM中执行,如果NAND flash中的程序小于4K的话,程序还能正常运行,如果大于4K,那大于4K的这部分就运行不了。所以我们就引入了重定位,NAND flash的代码中的前4K的代码中需要把SEO靠我整个代码拷贝到SDRAM去执行。

另外,对于NOR FLASH来说,虽然能在上面执行代码,但是我们却无法写NOR FLASH,所以一旦程序中有需要写的变量,比如全局变量和静态变量,我们在无法在NOR FSEO靠我LASH上直接修改它们的值。因此,我们还是需要将代码重定位到SDRAM中去执行。

这里重定位涉及到了链接脚本,编写出一个boot.lds如下:

/* 链接脚本 */ SECTIONS{/*SEO靠我 使用位置无关码,只用一个链接地址 也就是代码运行时地址的起始地址 */. = 0x33f80000; /* 刚好跟最高地址相差512k,足够用的 */.text : { *(.text) } /* SEO靠我所有文件的代码段 */. = ALIGN(4); /* 对齐 */.rodata : { *(.rodata) } /* 所有文件的只读数据段 */. = ALIGN(4);.data : { *(.SEO靠我data) } /* 所有文件的数据段 */. = ALIGN(4);__bss_start = .; /* 等于当前地址 */.bss : { *(.bss) *(COMMON) } /* 所有文件SEO靠我的bss段,程序运行之前先把这段内存清0 */__bss_end = .; }

重定位是为了将BootLoader本身的代码从flash复制到链接地址去。查看芯片手册得知:

我们的内存为6SEO靠我4M,基地址是0x30000000,所以在这里我们让sp指向最高地址就好了,栈是向下增长,因此0x30000000再加上64M就位0x34000000

代码如下:

ldr sp, =0x34000000 SEO靠我//设置栈 bl nand_init //不管是nor还是nand启动,都需要初始化nand flash,因为内核存在nand flash上

后续代码如下:

/* r0为第一个参数,r1为SEO靠我第二个参数,就是链接地址*/mov r0, #0 /* 源是0,就是从0地址开始读东西 */ldr r1, =_start /* 第一条标号的地址 */ldr r2, =__bss_start /* SEO靠我bss起始地址 */sub r2, r2, r1 /* 除去bss段之后的二进制文件的大小 */bl copy_code_to_sdram /* 对于bss段(没有初始化或者初始化为0的全局变量),不SEO靠我会存放在最后生成的二进制文件中,因此会把他清0 */bl clear_bss

注意到copy_code_to_sdram 这个函数需要三个参数,所以需要在上面写出对应的r0,r1,r2;

1、把地址0作为SEO靠我第一个参数(如果是NAND启动,就是NAND FLASH零地址的位置,如果是NOR启动,就是NOR FLASH 零地址的位置),为拷贝代码的源地址;

2、_start(为代码一开始的地址,也就是链接脚本SEO靠我中定义的0x33f80000)作为第二个参数,为代码拷贝的目的地址;

3、而要拷贝的代码有多长呢?这里就要用到有关ELF文件中BSS段的知识。我们都知道编译出来的bin文件是不包含BSS段的,BSS段存SEO靠我放的是未初始化的全局变量和静态变量,所以我们可以把它想象为初始化为0值,如果bin文件中存放一堆0值的变量是很浪费空间的。这里我们就知道,拷贝的代码长度为BSS段开始的地址减去代码的起始地址,也就是_SEO靠我_bss_start - _start 。

对于copy_code_to_sdram函数如下:

/*知识背景:*对于nand flash: 开机启动的时候,从0地址开始的前4k内容会被拷贝到*芯片的片内0SEO靠我地址开始的RAM里面,并在RAM的0地址开始执行,所以*我们可以读写0地址开始的内容。*而对于nor flash : 是能直接在nor flash读的,但是不能写,开机启动*是在nor flash的0SEO靠我地址处开始执行,所以我们能读但是写不了。*/ int isBootFromNorFlash(void) {volatile int *p = (volatile intSEO靠我 *)0;int val;val = *p;*p = 0x12345678;if(*p == 0x12345678){/*nand flash启动*/*p = val;return 0;}else{/SEO靠我*nor flash启动*/return 1;} }void copy_code_to_sdram(unsigned char *src, unsigned char *dest, uSEO靠我nsigned int len) {int i = 0;/*如果是NOR启动*/if(isBootFromNorFlash()){while(i < len){dest[i] = srSEO靠我c[i];i++;}}else{nand_read((unsigned int)src, dest, len);} }

对于nand_read()函数我们后面进行分析

对于clear_bsSEO靠我s函数如下:

void clean_bss(void) {extern int __bss_start, __bss_end;int *p = &__bss_start;for (; pSEO靠我<&__bss_end; p++)*p = 0; }

5、执行main函数

ldr lr, =haltldr pc, =main/* main函数有返回就会跳到这里,避免单板跑飞 */ SEO靠我 halt:b halt

到此,BootLoader第一阶段汇编部分就是实现了。

2.BootLoader第二阶段

首先注意一点:BootLoader不依赖于任何其他的代码,因此所有的函数都要自SEO靠我己实现。

就像前面说的,为了开发的方便,至少要初始化一个串口以便程序员与BootLoader交互。

1.初始化串口 对于串口的初始化如下:

/* UART registers*/ #definSEO靠我e ULCON0 (*(volatile unsigned long *)0x50000000) #define UCON0 (*(volatile unsigned long *)0SEO靠我x50000004) #define UFCON0 (*(volatile unsigned long *)0x50000008) #define UMCON0 (*(SEO靠我volatile unsigned long *)0x5000000c) #define UTRSTAT0 (*(volatile unsigned long *)0x50000010SEO靠我) #define UTXH0 (*(volatile unsigned char *)0x50000020) #define URXH0 (*(volatile unSEO靠我signed char *)0x50000024) #define UBRDIV0 (*(volatile unsigned long *)0x50000028)```#define SEO靠我PCLK 50000000 // init.c中的clock_init函数设置PCLK为50MHz #define UART_CLK PCLK // UART0的时钟源设为PCLK SEO靠我 #define UART_BAUD_RATE 115200 // 波特率 #define UART_BRD ((UART_CLK / (UART_BAUD_RATE * SEO靠我16)) - 1)/** 初始化UART0* 115200,8N1,无流控*/ void uart0_init(void) {GPHCON |= 0xa0; // GPSEO靠我H2,GPH3用作TXD0,RXD0GPHUP = 0x0c; // GPH2,GPH3内部上拉ULCON0 = 0x03; // 8N1(8个数据位,无较验,1个停止位)UCON0 = 0x05; SEO靠我// 查询方式,UART时钟源为PCLKUFCON0 = 0x00; // 不使用FIFOUMCON0 = 0x00; // 不使用流控UBRDIV0 = UART_BRD; // 波特率为11520SEO靠我0 }

2.从nand flash里面把内核读入内存

/* 1. 从NAND FLASH里把内核读入内存 */puts("Copy kernel from nand\n\r");nand_SEO靠我read(0x60000+64, (unsigned char *)0x30008000, 0x200000);puthex(0x1234ABCD);puts("\n\r");puthex(*p);pSEO靠我uts("\n\r");

这里注意: 对于nand flash有一个缺陷,就是位反转,因此在nand flash的结构图中会有一个OOB(out of bank)用来解决位反转

在写的时候除了写一页nanSEO靠我d flash之外,还要生成校验码,也就是ECC码,这个ECC码写到OOB中。

在读的时候除了读一页数据,还要把ECC码从OOB中读出来,读出来的数据要重新生成一个校验码,两个进行比较,如果相等,则读数SEO靠我据没错。有错误就会找出某一位,修正他的错误,

因此访问2048其实是在第二页,对于OBB,可以描述为OOB里的第几个字节

因此下面要实现nand_read()函数

/* NAND FLASH控制器 */ SEO靠我 #define NFCONF (*((volatile unsigned long *)0x4E000000)) #define NFCONT (*((volatile SEO靠我unsigned long *)0x4E000004)) #define NFCMMD (*((volatile unsigned char *)0x4E000008)) SEO靠我 #define NFADDR (*((volatile unsigned char *)0x4E00000C)) #define NFDATA (*((volatile unsigSEO靠我ned char *)0x4E000010)) #define NFSTAT (*((volatile unsigned char *)0x4E000020))void nand_inSEO靠我it(void) { #define TACLS 0 #define TWRPH0 1 #define TWRPH1 0/* 设置时序 SEO靠我*/NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);/* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */NFCONT = (1<<4)|(1SEO靠我<<1)|(1<<0); }void nand_select(void) {NFCONT &= ~(1<<1); }void nand_deselectSEO靠我(void) {NFCONT |= (1<<1); }void nand_cmd(unsigned char cmd) {volatile int i;SEO靠我NFCMMD = cmd;for (i = 0; i < 10; i++); }void nand_addr(unsigned int addr) {unsigned SEO靠我int col = addr % 2048;unsigned int page = addr / 2048;volatile int i;NFADDR = col & 0xff;for (i = 0;SEO靠我 i < 10; i++);NFADDR = (col >> 8) & 0xff;for (i = 0; i < 10; i++);NFADDR = page & 0xff;for (i = 0; iSEO靠我 < 10; i++);NFADDR = (page >> 8) & 0xff;for (i = 0; i < 10; i++);NFADDR = (page >> 16) & 0xff;for (iSEO靠我 = 0; i < 10; i++); }void nand_wait_ready(void) {while (!(NFSTAT & 1)); }unsSEO靠我igned char nand_data(void) {return NFDATA; }void nand_read(unsigned int addr, unsignSEO靠我ed char *buf, unsigned int len) {int col = addr % 2048;int i = 0;/* 1. 选中 */nand_select();whSEO靠我ile (i < len){/* 2. 发出读命令00h */nand_cmd(0x00);/* 3. 发出地址(分5步发出) */nand_addr(addr);/* 4. 发出读命令30h */nSEO靠我and_cmd(0x30);/* 5. 判断状态 */nand_wait_ready();/* 6. 读数据 */for (; (col < 2048) && (i < len); col++){buSEO靠我f[i] = nand_data();i++;addr++;}col = 0;}/* 7. 取消选中 */ nand_deselect(); }

辅助代码如下:

/** 发送一个字符*/ SEO靠我 void putc(unsigned char c) {/* 等待,直到发送缓冲区中的数据已经全部发送出去 */while (!(UTRSTAT0 & TXD0READSEO靠我Y));/* 向UTXH0寄存器中写入数据,UART即自动将它发送出去 */UTXH0 = c; }void puts(char *str) {int i = 0;whSEO靠我ile (str[i]){putc(str[i]);i++;} }void puthex(unsigned int val) {/* 0x1234abcd */int SEO靠我i;int j;puts("0x");for (i = 0; i < 8; i++){j = (val >> ((7-i)*4)) & 0xf;if ((j >= 0) && (j <= 9))putSEO靠我c(0 + j);elseputc(A + j - 0xa);} }

3.设置参数

怎么为内核设置启动参数?

BootLoader和内核的交互是单向的,方法就是BootLoader将参数放在SEO靠我某个约定的地方,再启动内核,内核启动后就去这个地址获得参数。

除了约定好参数的地址,还要规定参数的结构,是以标记列表TAG的形式来启动内核,标记就是一种数据结构,标记以标记 ATAG CORE开始,以标SEO靠我记 ATAG NONE结束。

标记的数据结构是tag,他是由一个tag_handler和一个联合体union组成

tag_hander结构体的两个成员分别表示类型和长度,比如表示的是内存还是命令行参数

对于SEO靠我不同类型的标记使用不同的联合体,比如内存使用tag_mem32,命令行参数使用tag_cmdline

参数的开始:setup_start_tag

参数的结束:setup_end_tag/* 2. 设置参数SEO靠我 */puts("Set boot params\n\r");setup_start_tag();setup_memory_tags();setup_commandline_tag("noinitrdSEO靠我 root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");setup_end_tag();

上述代码中我们用到了4个函数,来源于这里:

那我们可以模仿这四个SEO靠我函数

1.setup_start_tag(bd)函数:static void setup_start_tag (bd_t *bd) {/* static struct tag *paraSEO靠我ms; */ /* gd->bd->bi_boot_params = 0x30000100; */params = (struct tag *) bd->bi_boot_params;params->SEO靠我hdr.tag = ATAG_CORE; /* 表示一个参数的开始 *//*#define tag_size(type) ((sizeof(struct tag_header) + sizeof(stSEO靠我ruct type)) >> 2) *//* 4+4 3*4 */params->hdr.size = tag_size (tag_core); /* 20 >> 2 = 20/4 = 5 以4字节为SEO靠我单位 *//* 一共5个,tag和size占据两个,还有三个用来存储core里面的三个熟悉 *//* 都设置为0,说明没有用到这些 */params->u.core.flags = 0;params-SEO靠我>u.core.pagesize = 0;params->u.core.rootdev = 0;params = tag_next (params); /* 下一个参数的位置 = 当前位置 + 头部的SEO靠我size;指针加5,相当于加上5*4*/ }

对于tag,定义如下:

struct tag {struct tag_header hdr; /* tag的头部 */union {strucSEO靠我t tag_core core;struct tag_mem_range mem_range;struct tag_cmdline cmdline;struct tag_clock clock;strSEO靠我uct tag_ethernet ethernet;} u; };

他有一个头部tag_hander,定义如下:

struct tag_header {u32 size;u32 tag; SEO靠我 };

2.setup_memory_tags (bd)函数:

static void setup_memory_tags (bd_t *bd) /* tags说明可以设置多个memory SEO靠我*/ {int i;for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {params->hdr.tag = ATAG_MEM;params->hdrSEO靠我.size = tag_size (tag_mem32);params->u.mem.start = bd->bi_dram[i].start; /* 起始地址 */params->u.mem.sizSEO靠我e = bd->bi_dram[i].size; /* 大小 */params = tag_next (params);} }

3.setup_commandline_tag函数:

staSEO靠我tic void setup_commandline_tag (bd_t *bd, char *commandline) /* 设置命令行参数 */ {char *p;if (!comSEO靠我mandline)return;/* eat leading white space */for (p = commandline; *p == ; p++);/* skip non-existentSEO靠我 command lines so the kernel will still* use its default command line.*/if (*p == \0)return;params->SEO靠我hdr.tag = ATAG_CMDLINE;params->hdr.size =(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2; /* SEO靠我向4取整 */strcpy (params->u.cmdline.cmdline, p);params = tag_next (params); }

4.setup_end_tag 函数SEO靠我

static void setup_end_tag (bd_t *bd) {params->hdr.tag = ATAG_NONE;params->hdr.size = 0; SEO靠我 }

上面给出的是源码里面的实现方法,下面我们自己实现:

static struct tag *params;void setup_start_tag(void) {params SEO靠我= (struct tag *)0x30000100;params->hdr.tag = ATAG_CORE;params->hdr.size = tag_size (tag_core);paramsSEO靠我->u.core.flags = 0;params->u.core.pagesize = 0;params->u.core.rootdev = 0;params = tag_next (params)SEO靠我; }void setup_memory_tags(void) {params->hdr.tag = ATAG_MEM;params->hdr.size = tag_sSEO靠我ize (tag_mem32);params->u.mem.start = 0x30000000;params->u.mem.size = 64*1024*1024;params = tag_nextSEO靠我 (params); }void setup_commandline_tag(char *cmdline) {int len = strlen(cmdline) + 1SEO靠我;params->hdr.tag = ATAG_CMDLINE;params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2;strcpSEO靠我y (params->u.cmdline.cmdline, cmdline);params = tag_next (params); }void setup_end_tag(void)SEO靠我 {params->hdr.tag = ATAG_NONE;params->hdr.size = 0; }

对于上面4个函数,在内存中的分布如下:

以上我们就设置好了参数,SEO靠我下面就是要跳转执行

4.跳转执行kernel这一步就更加简单了,我们都知道函数的名字其实就是一个地址值而已,我们前面已经把内核拷贝到内存0x3000800的地方,只要将这个地址值赋给函数指针变量thekSEO靠我ernel,再执行就可以了,并传递相应的参数,比如tag的起始地址。

theKernel = (void (*)(int, int, unsigned int))0x30008000;theKernelSEO靠我(0, 362, 0x30000100); /* * mov r0, #0* ldr r1, =362* ldr r2, =0x30000100* mov pc, #0x30008000 */

voidSEO靠我 (*theKernel)(int zero, int arch, uint params);

第一个参数:0 相当于mov r0, #0

第二个参数:机器ID,单板属于那个ID 相当于mov r1, #SEO靠我362

第三个参数:参数的位置

为什么要为内核传递参数?

首先在第一阶段里面是进行一些初始化的工作,这是为了使用开发板,但是内核并不是适配所有的开发板,

内核对于开发板的环境一无所知,因此想要启动内核,还需要SEO靠我给他传递一些参数,告诉内核当前所处的环境

怎么传递参数?

前面讲过三个参数,先将机器ID通过R1传递给内核,内核运行的时候会去从R1中取出机器ID进行分析

是否支持当前机器,这个机器ID实质上就是开发板CPSEO靠我U的ID。再传递参数的位置,也就是这块内存的基地址

这块内存中存放的就是Uboot给linux内核的其他参数,有起始地址、内存大小等,这个参数需要按照指定的

格式,并且还要规定参数的结构,也就是TAG

5.SEO靠我改进

启动内核花了7s,时间很长。主要是花在nand_read里面,这个可以改进

1、提高CPU频率,200MHz—>400MHz

2、启动ICACHE

读协处理器,然后写协处理器 ---->最终不到2ms就SEO靠我读出来了

2440里有CPU,CPU里面有cache,cache里面有指令ICACHE,数据DCACHE。我们的程序在SDRAM中。

如果不使用指令cache,CPU取完指令回来执行,每执行一条指令都回去SEO靠我访问SDRAM,代码不断执行,不断取

但是当有了ICACEH之后,CPU去取指令的时候会把那一块指令全部放到ICACHE中,CPU下次取指令的时候会先去ICACHE中找有没有指令,有的话直接取出来执行

访SEO靠我问外部SDRAM还要发出各种命令来读内存,非常耗时

cache就是高速内存

DCACHE能够使用的前提是MMU要启动

以上我们就是实现了一个简单的bootloader

代码如下:

boot.lds:SECTIOSEO靠我NS {. = 0x33f80000;.text : { *(.text) }. = ALIGN(4);.rodata : {*(.rodata*)} . = ALIGN(4);.data : { *SEO靠我(.data) }. = ALIGN(4);__bss_start = .;.bss : { *(.bss) *(COMMON) }__bss_end = .; }

init.c:

/* SEO靠我NAND FLASH控制器 */ #define NFCONF (*((volatile unsigned long *)0x4E000000)) #define NFSEO靠我CONT (*((volatile unsigned long *)0x4E000004)) #define NFCMMD (*((volatile unsigned char *)0SEO靠我x4E000008)) #define NFADDR (*((volatile unsigned char *)0x4E00000C)) #define NFDATA SEO靠我(*((volatile unsigned char *)0x4E000010)) #define NFSTAT (*((volatile unsigned char *)0x4E00SEO靠我0020))/* GPIO */ #define GPHCON (*(volatile unsigned long *)0x56000070) #define GPHUSEO靠我P (*(volatile unsigned long *)0x56000078)/* UART registers*/ #define ULCON0 (*(volatile unsiSEO靠我gned long *)0x50000000) #define UCON0 (*(volatile unsigned long *)0x50000004) #definSEO靠我e UFCON0 (*(volatile unsigned long *)0x50000008) #define UMCON0 (*(volatile unsigned long *)SEO靠我0x5000000c) #define UTRSTAT0 (*(volatile unsigned long *)0x50000010) #define UTXH0 (SEO靠我*(volatile unsigned char *)0x50000020) #define URXH0 (*(volatile unsigned char *)0x50000024)SEO靠我 #define UBRDIV0 (*(volatile unsigned long *)0x50000028)#define TXD0READY (1<<2)void nand_reSEO靠我ad(unsigned int addr, unsigned char *buf, unsigned int len);int isBootFromNorFlash(void) {voSEO靠我latile int *p = (volatile int *)0;int val;val = *p;*p = 0x12345678;if (*p == 0x12345678){/* 写成功, 是naSEO靠我nd启动 */*p = val;return 0;}else{/* NOR不能像内存一样写 */return 1;} }void copy_code_to_sdram(unsignedSEO靠我 char *src, unsigned char *dest, unsigned int len) { int i = 0;/* 如果是NOR启动 */if (isBootFromNSEO靠我orFlash()){while (i < len){dest[i] = src[i];i++;}}else{//nand_init();nand_read((unsigned int)src, deSEO靠我st, len);} }void clear_bss(void) {extern int __bss_start, __bss_end;int *p = &__bss_SEO靠我start;for (; p < &__bss_end; p++)*p = 0; }void nand_init(void) { #define TACSEO靠我LS 0 #define TWRPH0 1 #define TWRPH1 0/* 设置时序 */NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWSEO靠我RPH1<<4);/* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */NFCONT = (1<<4)|(1<<1)|(1<<0); }void nand_selectSEO靠我(void) {NFCONT &= ~(1<<1); }void nand_deselect(void) {NFCONT |= (1<<1); SEO靠我 }void nand_cmd(unsigned char cmd) {volatile int i;NFCMMD = cmd;for (i = 0; i < 10; i++);SEO靠我 }void nand_addr(unsigned int addr) {unsigned int col = addr % 2048;unsigned int pagSEO靠我e = addr / 2048;volatile int i;NFADDR = col & 0xff;for (i = 0; i < 10; i++);NFADDR = (col >> 8) & 0xSEO靠我ff;for (i = 0; i < 10; i++);NFADDR = page & 0xff;for (i = 0; i < 10; i++);NFADDR = (page >> 8) & 0xfSEO靠我f;for (i = 0; i < 10; i++);NFADDR = (page >> 16) & 0xff;for (i = 0; i < 10; i++); }void nandSEO靠我_wait_ready(void) {while (!(NFSTAT & 1)); }unsigned char nand_data(void) {reSEO靠我turn NFDATA; }void nand_read(unsigned int addr, unsigned char *buf, unsigned int len) SEO靠我 {int col = addr % 2048;int i = 0;/* 1. 选中 */nand_select();while (i < len){/* 2. 发出读命令00h */nand_cmSEO靠我d(0x00);/* 3. 发出地址(分5步发出) */nand_addr(addr);/* 4. 发出读命令30h */nand_cmd(0x30);/* 5. 判断状态 */nand_wait_rSEO靠我eady();/* 6. 读数据 */for (; (col < 2048) && (i < len); col++){buf[i] = nand_data();i++;addr++;}col = 0SEO靠我;}/* 7. 取消选中 */ nand_deselect(); }#define PCLK 50000000 // init.c中的clock_init函数设置PCLK为50MHz SEO靠我 #define UART_CLK PCLK // UART0的时钟源设为PCLK #define UART_BAUD_RATE 115200 // 波特率 SEO靠我 #define UART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)/** 初始化UART0* 115200,8N1,无流控*/ voSEO靠我id uart0_init(void) {GPHCON |= 0xa0; // GPH2,GPH3用作TXD0,RXD0GPHUP = 0x0c; // GPH2,GPH3内部上拉ULSEO靠我CON0 = 0x03; // 8N1(8个数据位,无较验,1个停止位)UCON0 = 0x05; // 查询方式,UART时钟源为PCLKUFCON0 = 0x00; // 不使用FIFOUMCONSEO靠我0 = 0x00; // 不使用流控UBRDIV0 = UART_BRD; // 波特率为115200 }/** 发送一个字符*/ void putc(unsignedSEO靠我 char c) {/* 等待,直到发送缓冲区中的数据已经全部发送出去 */while (!(UTRSTAT0 & TXD0READY));/* 向UTXH0寄存器中写入数据,UARTSEO靠我即自动将它发送出去 */UTXH0 = c; }void puts(char *str) {int i = 0;while (str[i]){putc(str[i]);SEO靠我i++;} }void puthex(unsigned int val) {/* 0x1234abcd */int i;int j;puts("0x");for (i SEO靠我= 0; i < 8; i++){j = (val >> ((7-i)*4)) & 0xf;if ((j >= 0) && (j <= 9))putc(0 + j);elseputc(A + j - SEO靠我0xa);}}

boot.c:

#include "setup.h"extern void uart0_init(void); extern void nand_read(unsignedSEO靠我 int addr, unsigned char *buf, unsigned int len); extern void puts(char *str); exterSEO靠我n void puthex(unsigned int val);static struct tag *params;void setup_start_tag(void) {paramsSEO靠我 = (struct tag *)0x30000100;params->hdr.tag = ATAG_CORE;params->hdr.size = tag_size (tag_core);paramSEO靠我s->u.core.flags = 0;params->u.core.pagesize = 0;params->u.core.rootdev = 0;params = tag_next (paramsSEO靠我); }void setup_memory_tags(void) {params->hdr.tag = ATAG_MEM;params->hdr.size = tag_SEO靠我size (tag_mem32);params->u.mem.start = 0x30000000;params->u.mem.size = 64*1024*1024;params = tag_nexSEO靠我t (params); }int strlen(char *str) {int i = 0;while (str[i]){i++;}return i; SEO靠我}void strcpy(char *dest, char *src) {while ((*dest++ = *src++) != \0); }void setup_cSEO靠我ommandline_tag(char *cmdline) {int len = strlen(cmdline) + 1;params->hdr.tag = ATAG_CMDLINE;SEO靠我params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2;strcpy (params->u.cmdline.cmdline, cmSEO靠我dline);params = tag_next (params); }void setup_end_tag(void) {params->hdr.tag = ATAGSEO靠我_NONE;params->hdr.size = 0; }int main(void) {void (*theKernel)(int zero, int arch, uSEO靠我nsigned int params);volatile unsigned int *p = (volatile unsigned int *)0x30008000;/* 0. 帮内核设置串口: 内核SEO靠我启动的开始部分会从串口打印一些信息,但是内核一开始没有初始化串口 */uart0_init();/* 1. 从NAND FLASH里把内核读入内存 */puts("Copy kernel from nSEO靠我and\n\r");nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000);puthex(0x1234ABCD);puts("\n\rSEO靠我");puthex(*p);puts("\n\r");/* 2. 设置参数 */puts("Set boot params\n\r");setup_start_tag();setup_memory_tSEO靠我ags();setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");setup_end_SEO靠我tag();/* 3. 跳转执行 */puts("Boot kernel\n\r");theKernel = (void (*)(int, int, unsigned int))0x30008000;SEO靠我theKernel(0, 362, 0x30000100); /* * mov r0, #0* ldr r1, =362* ldr r2, =0x30000100* mov pc, #0x300080SEO靠我00 */puts("Error!\n\r");/* 如果一切正常, 不会执行到这里 */return -1; }

start.s:

#define S3C2440_MPLL_200MHZSEO靠我 ((0x5c<<12)|(0x01<<4)|(0x02)) #define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01)) SEO靠我 #define MEM_CTL_BASE 0x48000000.text .global _start _start:/* 1. 关看门狗 */ldr r0,SEO靠我 =0x53000000mov r1, #0str r1, [r0]/* 2. 设置时钟 */ldr r0, =0x4c000014// mov r1, #0x03; // FCLK:HCLK:PCLSEO靠我K=1:2:4, HDIVN=1,PDIVN=1mov r1, #0x05; // FCLK:HCLK:PCLK=1:4:8str r1, [r0]/* 如果HDIVN非0,CPU的总线模式应该从“fSEO靠我ast bus mode”变为“asynchronous bus mode” */mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */ orr r1, r1, #0xc000SEO靠我0000 /* 设置为“asynchronous bus mode” */mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 *//* MPLLCON = S3C2440_MPLSEO靠我L_200MHZ */ldr r0, =0x4c000004ldr r1, =S3C2440_MPLL_400MHZstr r1, [r0]/* 启动ICACHE */mrc p15, 0, r0, SEO靠我c1, c0, 0 @ read control regorr r0, r0, #(1<<12)mcr p15, 0, r0, c1, c0, 0 @ write it back/* 3. 初始化SDSEO靠我RAM */ldr r0, =MEM_CTL_BASEadr r1, sdram_config /* sdram_config的当前地址 */add r3, r0, #(13*4) 1SEO靠我:ldr r2, [r1], #4str r2, [r0], #4cmp r0, r3bne 1b/* 4. 重定位 : 把bootloader本身的代码从flash复制到它的链接地址去 */ldr SEO靠我sp, =0x34000000bl nand_initmov r0, #0ldr r1, =_startldr r2, =__bss_startsub r2, r2, r1bl copy_code_tSEO靠我o_sdrambl clear_bss/* 5. 执行main */ldr lr, =haltldr pc, =main halt:b haltsdram_config:.long 0SEO靠我x22011110 //BWSCON.long 0x00000700 //BANKCON0.long 0x00000700 //BANKCON1.long 0x00000700 //BANKCON2.SEO靠我long 0x00000700 //BANKCON3 .long 0x00000700 //BANKCON4.long 0x00000700 //BANKCON5.long 0x00018005 //SEO靠我BANKCON6.long 0x00018005 //BANKCON7.long 0x008C04F4 // REFRESH.long 0x000000B1 //BANKSIZE.long 0x000SEO靠我00030 //MRSRB6.long 0x00000030 //MRSRB7

Makefile:

CC = arm-linux-gcc LD = arm-linux-ld SEO靠我 AR = arm-linux-ar OBJCOPY = arm-linux-objcopy OBJDUMP = arm-linux-objdumpCFLAGS := SEO靠我-Wall -O2 CPPFLAGS := -nostdinc -nostdlib -fno-builtinobjs := start.o init.o boot.oboot.bin:SEO靠我 $(objs)${LD} -Tboot.lds -o boot.elf $^${OBJCOPY} -O binary -S boot.elf $@${OBJDUMP} -D -m arm boot.SEO靠我elf > boot.dis%.o:%.c${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<%.o:%.S${CC} $(CPPFLAGS) $(CFLAGS) -c -oSEO靠我 $@ $<clean:rm -f *.o *.bin *.elf *.dis
“SEO靠我”的新闻页面文章、图片、音频、视频等稿件均为自媒体人、第三方机构发布或转载。如稿件涉及版权等问题,请与 我们联系删除或处理,客服邮箱:html5sh@163.com,稿件内容仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同 其观点或证实其内容的真实性。

网站备案号:浙ICP备17034767号-2