原创自己动手编写嵌入式Bootloader之1.docx

上传人:b****8 文档编号:24021836 上传时间:2023-05-23 格式:DOCX 页数:14 大小:159.69KB
下载 相关 举报
原创自己动手编写嵌入式Bootloader之1.docx_第1页
第1页 / 共14页
原创自己动手编写嵌入式Bootloader之1.docx_第2页
第2页 / 共14页
原创自己动手编写嵌入式Bootloader之1.docx_第3页
第3页 / 共14页
原创自己动手编写嵌入式Bootloader之1.docx_第4页
第4页 / 共14页
原创自己动手编写嵌入式Bootloader之1.docx_第5页
第5页 / 共14页
点击查看更多>>
下载资源
资源描述

原创自己动手编写嵌入式Bootloader之1.docx

《原创自己动手编写嵌入式Bootloader之1.docx》由会员分享,可在线阅读,更多相关《原创自己动手编写嵌入式Bootloader之1.docx(14页珍藏版)》请在冰豆网上搜索。

原创自己动手编写嵌入式Bootloader之1.docx

原创自己动手编写嵌入式Bootloader之1

第一部分:

基本功能流程

CPU上电后会从IO空间的某地址取第一条指令。

但此时:

PLL没有启动,CPU工作频率为外部输入晶振频率,非常低;CPU工作模式、中断设置等不确定;存储空间的各个BANK(包括内存)都没有驱动,内存不能使用。

在这种情况下必须在第一条指令处做一些初始化工作,这段初始化程序与操作系统独立分开,称之为bootloader。

实际上,很少有必要自己写一个Bootloader,因为U-Boot已经强大到能够满足各种需要。

但是强大必然复杂,一个初学者想要分析U-Boot的源代码,还是有些难度的。

出于学习的目的,我写了这个史上最简单的启动加载器,它只包含最基本的功能,却囊括了一个嵌入式Bootloader应该有的核心和精华。

我把这个启动加载器命名为S-Boot,是SimpleBootloader的缩写,亦可进一步简称为SB。

使用的实验环境为OK2440开发板,板上处理器为S3C2440A,有64M内存,Nand存储器为K9F1208,64M。

网口芯片为CS8900A。

我们要实现的功能是:

从串口下载Linux内核映像到RAM;从网口下载Linux内核映像到RAM;从RAM启动内核挂载NFS根文件系统。

1.第一阶段的汇编代码:

start.S

一个嵌入式Bootloader最初始部分的代码几乎必须是用汇编语言写成的,因为开发板刚上电后没有准备好C程序运行环境,比如堆栈指针SP没有指到正确的位置。

汇编代码应该完成最原始的硬件设备初始化,并准备好C运行环境,这样后面的功能就可以用C语言来写了。

对我们的S-Boot来说,上电后的起始运行代码是start/start.S。

.text

.global_start

_start:

bReset@0x00:

发生复位异常时从地址零处开始运行

    bHandleUndef@0x04:

未定义指令中止模式的向量地址

    bHandleSWI  @0x08:

管理模式的向量地址,通过SWI指令进入此模式

    bHandlePrefetchAbort@0x0C:

指令预取终止导致的异常的向量地址

    bHandleDataAbort    @0x10:

数据访问终止导致的异常的向量地址

    bHandleNotUsed      @0x14:

保留

    bHandleIRQ          @0x18:

中断模式的向量地址

    bHandleFIQ          @0x1C:

快中断模式的向量地址

 

这里,汇编指示符.text表明以下内容属于代码段,.global_start指明_start是全局可访问的符号(Givethesymbolexternallinkage)。

按照ARM920T的规定,从地址0x00到0x1C放置异常向量表,向量表每个条目占四个字节,正好可以放置一条跳转指令,跳转到相应异常的服务程序中去。

在S-Boot中没有使用中断,所以除Reset异常外,其它异常的服务程序都可简单地写个死循环。

Reset异常是系统上电后自动触发的,所以我们的代码都写在Reset的服务程序里面。

 

实际上,异常向量表不一定非要位于地址0x00处,CP15协处理器中的c1寄存器的第13位用来控制异常向量表的起始地址。

该位为0时,异常向量表位于低地址0x00处;该位为1时,异常向量表位于高地址0xFFFF0000处。

我们没有必要改变这个位的值,使用默认的低地址就行了。

 

Reset:

        mrsr0,cpsr      @setcputoSVC32mode

        bicr0,r0,#0x1F

        orrr0,r0,#0xD3

        msrcpsr,r0      @cpsr=11x10011,IRQ/FIQdisabled

 

代码最初始的任务是设置CPU工作在SVC32模式,关闭所有中断,禁用看门狗。

实际上,即使不设置工作模式,CPU在复位之后将自动工作在管理模式。

在整个S-Boot运行期间,我们没有使用中断,也没有改变CPU工作模式,它将一直工作在SVC32模式。

 

MMU、ICache、DCache的打开和关闭都是由CP15协处理器的c1寄存器控制的。

实际上在复位之后这三者都是自动关闭的,所以省略了关闭它们的代码。

S3C2440A的PSR寄存器(ProgramStatusReguster)中每个Bit位的含义如图1所示。

Bit4~Bit0为模式位,用来设置CPU工作模式,现在只要知道M[4:

0]=10011表示SVC32模式就行了。

Bit5为状态位,T=0表示工作在ARM状态,T=1表示工作在Thumb状态,默认为0,不需要改变。

Bit6为快速中断禁止位,F=1为禁止快速中断,F=0为使能快速中断。

Bit7为中断禁止位,I=1为禁止中断,F=0为使能中断。

其它Bit位暂时可以不必理会。

mrs和msr是在PSR寄存器和其它寄存器间传递数据的指令。

如:

mrsr0,cpsr把cpsr的值传送到r0中,msrcpsr,r0把r0的值传送到cpsr中。

bic是位清零(BitClear)指令,bicr0,r0,#0x1F意思是把r0的Bit[4:

0]位清零(由0x1F指示),然后把结果写入r0中。

orr是按位求或指令,orrr0,r0,#0xD3表示把r0的Bit7,Bit6,Bit4,Bit1,Bit0置为1,其它位保持不变。

执行完上述操作后,cpsr中的I=1,F=1,T保持不变(默认为0),M[4:

0]=10011,意思是禁止IRQ,禁止FIQ,工作在ARM状态,工作在SVC32模式。

 

 

       ldrr0,=0x53000000

        movr1,#0x0

        strr1,[r0]@disablewatchdog

 

禁用看门狗更简单,因为WTCON寄存器的地址为0x53000000,直接向该寄存器写0即可。

到目前为止,CPU工作在外接晶振12MHz频率之下。

使用以下代码设置PLL,提升工作频率。

 

 

       ldrr0,=0x4C000014@CLKDIVNregister

        movr1,#0x05@FCLK:

HCLK:

PCLK=1:

4:

8

        strr1,[r0]

        mrcp15,0,r0,c1,c0,0@ifHDIVNNot0,mustasynchronousbusmode

        orrr0,r0,#0xC0000000@seeS3C2440AmanualP7-9

        mcrp15,0,r0,c1,c0,0

        ldrr0,=0x4C000004@MPLLCONregister

        ldrr1,=0x0005C011@((92<<12)|(1<<4)|

(1))

        strr1,[r0]@FCLKis400MHz!

 

最后的结果是,FCLK=400MHz,HCLK=100MHz,PCLK=50MHz。

 

       @SDRAMInit

        movr1,#0x48000000@MEM_CTL_BASE

        adrlr2,mem_cfg_val

        addr3,r1,#52

1:

        ldrr4,[r2],#4@读取设置值,并让r2加4

        strr4,[r1],#4@将此值写入寄存器,并让r1加4

        cmpr1,r3@判断是否设置完所有13个寄存器

        bne1b@若没有写成,继续

 

设置存储控制器。

 

       ldrsp,=0x32FFF000@设置堆栈

        blnand_init@初始化NANDFlash

                                            @nand_read_ll函数需要3个参数:

        ldrr0,=0x33000000@1.目标地址=0x30000000,这是SDRAM的起始地址

        movr1,#0@2.源地址=0,S-Boot代码都存在NAND地址0开始处

        movr2,#102400@3.复制长度=102400(bytes)

        blnand_read@调用C函数nand_read

        ldrlr,=halt_loop@设置返回地址

        ldrpc,=main@b指令和bl指令只能前后跳转32M的范围,故使用向pc赋值的方法进行跳转

halt_loop:

        bhalt_loop

 

这里把所有的代码从Nand拷贝到RAM中,然后跳转到main函数去执行。

此后程序便在RAM中运行了。

但是到目前为止,前面的程序都是在SteppingStone里运行的。

所谓SteppingStone,是指在S3C2440A的内部的4KB的RAM缓存,它总是映射到地址0x00处。

硬件加电后会自动将NandFlash中的前4KB的数据拷贝到SteppingStone中,然后从地址0x00处开始运行。

如果代码足够小(小于4KB)的话,那只在SteppingStone中运行,加载Linux内核到内存即可。

但通常代码肯定会大于4KB。

所以Bootloader一般分为两部分,Stage1的代码在SteppingStone中运行,它会把Stage2的代码拷贝到RAM中,并跳转到RAM中执行;Stage2的代码在RAM中执行,它可以完成加载内核及其它任何复杂的功能。

因为Stage2的起始位置不好确定,为了方便,我们把所有的代码都拷贝到RAM中了。

C函数nand_read有三个参数,第一个参数为目的地起始地址,第二个参数为源起始地址,第三个参数为要复制的数据长度,以字节为单位。

根据ATPCS函数调用规则,三个参数分别用寄存器r0,r1,r2来传递。

我们在内存的0x33000000处存放Bootloader,复制长度根据编译生成的S-Boot.bin映像文件大小,向上取512字节的整数倍。

这里先来规划一下内存空间的分配。

RAM的地址范围是从0x30000000到0x34000000共64MByte。

把S-Boot和Kernel放在高地址处,S-Boot从0x33000000开始,预留8MByte的空间,内核从0x33800000开始,可供使用的空间也是8MByte。

因栈空间是向下生长的,我们在S-Boot下面预留4096Byte的空闲区域,然后向下为栈空间,故栈指针SP初始化为0x32FFF000。

其实留不留空闲区域是无所谓的,这里只是为了把二者更明显地区分开。

我们只设置SVC模式下的SP,不使用CPU的其它工作模式,所以也没必要设置其它模式下的栈指针。

另外,程序中不使用动态内存分配,故而也不必分配堆空间。

2.nand读操作

在编译连接时,我们把上述start.S代码放在生成的二进制映像文件的最开始位置,因而也被烧写到NandFlash的最起始位置,因而会被自动拷贝到SteppingStone里运行。

start.S要完成的任务之一,是把S-Boot的所有代码从NandFlash拷贝到内存中,这里需要对NAND的读操作,因此对NAND的初始化和读操作要在第一阶段写好。

以开发板上使用的K9F1208为例,每个页(page)为512Byte数据和16Byte校验,每个块(Block)为32个页,即16KByte数据和512Byte校验。

NandFlash只用8根线与CPU的DATA0-7连接,位宽为8位,不管是数据、地址或控制字都通过这8根线传递,如果读写数据的话每次只能传输一个字节数据。

NandFlash的操作通过NFCONF、NFCMD、NFADDR、NFDATA、NFSTAT和NFECC六个寄存器来完成。

在S3C2440A数据手册第218页可以看到读写NandFlash的操作时序:

1. 通过NFCONF寄存器配置NandFlash;2.写NandFlash命令到NFCMD寄存器;3.写NandFlash地址到NFADDR寄存器;4. 在读写数据时,通过NFSTAT寄存器获得NandFlash的状态信息。

应该在读操作前或写操作后检查R/nB信号(Ready/Busy信号)。

初始化NANDFlash:

S3C2440的NFCONF寄存器用来设置时序参数TACLS、TWRPH0、TWRPH1,设置数据位宽;还有一些只读位。

TACLS、TWRPH0、TWRPH1这三个参数控制的是NandFlash信号线CLE/ALE与写控制信号nWE的时序关系。

注意,寄存器值转换成实际的时钟周期值时,TACLS不需加1,而TWRPH0和TWRPH1需要加1。

比如NFCONF寄存器中设置TACLS=1,TWRPH0=3,TWRPH1=0,意思是时序图中TACLS=1个HCLK时钟,TWRPH0=4个HCLK时钟,TWRPH1=1个HCLK时钟。

 

 

voidnand_init(void)

{

    //时间参数设为:

TACLS=0TWRPH0=3TWRPH1=0

   NFCONF=0x300;

    /*使能NANDFlash控制器,初始化ECC,禁止片选*/

    NFCONT=(1<<4)|(1<<1)|(1<<0);

    /*复位NANDFlash*/

    NFCONT&=~(1<<1);//发出片选信号

    NFCMMD=0xFF;//复位命令

    s3c2440_wait_idle();//循环查询NFSTAT位0,直到它等于1

    NFCONT|=0x2;//取消片选信号

}

读操作:

读操作也是以页(512Byte)为单位进行的。

在初始上电时,器件进入缺省的“读方式1模式”。

在这一模式下,页读操作通过将0x00写入指令寄存器,接着写入3个地址(1个列地址和2个行地址)来启动。

一旦页读指令被器件锁存,下面的页读操作就不需要再重复写入页读指令了。

写入页读指令和地址后,处理器可以通过对信号线R//B的分析来判断页读操作是否完成。

如果信号为低电平,表示器件正忙;如果信号为高电平,表示器件内部操作完成,要读取的数据被送入了数据寄存器。

外部控制器可以再以50ns为周期的连续/RE脉冲信号的控制下,从IO口依次读出数据。

连续页读操作中,输出的数据是从指定的列地址开始,直到该页最后一个列地址的数据为止。

 

    for(i=start_addr;i<(start_addr+size);)

     {

         NFCMMD=0;//发出READ0命令

         s3c2440_write_addr(i);//WriteAddress

         s3c2440_wait_idle();//循环查询NFSTAT位0,直到它等于1

         for(j=0;j

         {

             *buf=(unsignedchar)NFDATA;

             buf++;

         }

     }

 

缺点:

没有使用ECC校验和纠错;没有使用坏块检查;

3.main函数

串口初始化,以便能够向用户输出一些信息;网口初始化,以便能够从主机下载内核映像;输出一些菜单,以便用户选择执行所需要的功能。

比如,用户可以选择从串口或网口下载内核映像到RAM中某个地址,然后运行这个内核。

关于下载内核映像的实现,在后文会详细介绍。

这里只看当内核映像已经存在于RAM中时,怎样才能把这个内核启动起来。

4.启动参数的传递

启动Linux内核之前需要设置好一些必要的启动参数,这些参数以TAG列表的形式传递给内核。

所谓TAG列表,就是多个TAG在内存空间中按顺序排列。

每个TAG,其实都是一个结构体,每个结构体中又包含了一个头部结构体和一个内容结构体称。

头部结构体指明了本TAG的类型、占用空间大小;所谓TAG的类型,就是一个宏定义,用一个确定的整数来识别该标记。

内容结构体包含了该TAG的具体内容。

下面以具体的例子做说明。

在atag.h中就有:

#defineATAG_CORE0x54410001

#defineATAG_MEM0x54410002

#defineATAG_CMDLINE0x54410009

#defineATAG_NONE0x00000000

这些都是TAG的类型,注意这些整数跟地址没有关系,只是一个用来识别标记类型的符号而已。

每个Tag都用结构体表示,包含TagHeader头结构体以及随后的参数值数据结构。

如ATAG_CORE:

structAtag{

           structTagHeader stHdr;

           structTagCorestCore;

};

其中包含两个结构体。

第一个结构体TagHeader含两个整型变量,用以表示本结构体的长度、标记类型;nSzie赋值为头部TagHeader和数据TagCore的大小之和,注意是以字(即4字节)为单位;ulTag就赋值为先前定义的宏ATAG_CORE。

第二个结构体就是实际的数据了。

structTagHeader{

UINT32nSize;

UINT32ulTag;

};

structTagCore{

UINT32ulFlags;

UINT32nPageSize;

UINT32ulRootDev;

};

由于每个Tag都由一个TagHeader加一个数据部分组成,因此通常的做法是使用Struct和Union相结合来定义:

structAtag{

           structTagHeaderstHdr;

           union{

                  structTagCorestCore;

                  structTagMem32stMem;

                  structTagVideoTextstVideoText;

                  structTagRamDiskstRamDisk;

                  structTagInitrdstInitRd;

                  structTagSerialnrstSerialNr;

                  structTagRevisionstRevision;

                  structTagVideolfbstVideoLfb;

                  structTagCmdlinestCmdLine;

           };

};

其中涉及到的所有数据结构均可在Linux内核源码的include/asm/setup.h头文件找到,我们把这些定义放在Bootloader的头文件atag.h中。

启动参数标记列表以标记ATAG_CORE开始,以标记ATAG_NONE结束。

每个标记由标识被传递参数的tag_header结构以及随后的参数值数据结构来组成。

数据结构tag和tag_header定义在Linux内核源码的include/asm/setup.h头文件中,在我们的S-Boot中对应的头文件为atag.h。

在嵌入式Linux系统中,通常需要由BootLoader设置的常见启动参数有:

ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。

向内核传递参数的方法,先在内存中某个起始地址开始,连续存放多个Tag,组成Tag列表。

列表中的每个Tag包括头部TagHeader和数据结构体。

按规定,第一个Tag必须是ATAG_CORE,最末一个Tag必须是ATAG_NONE,而且中间必须包含至少一个ATAG_MEM。

注意的是末尾的ATAG_NONE只包括头部,没有数据内容。

如图所示。

在编程时先定义好起始地址,然后用一个指针,每设置完毕一个Tag的内容就向后移动相应的长度,然后设置下一个Tag内容,以保证各个Tag的连续存放。

下面具体说明几个关键Tag的数据区域内容的设置。

structTagCore结构体已经在前面列出,它包含三个整型变量,ulFlags一般设为零,nPageSize表示分页内存管理中每一页的大小,一般为4096字节,ulRootDev是系统启动的设备号,设为零即可,因为通常在后面的命令行参数Cmdline中覆盖这个设置。

StructTagMem用来描述系统的物理内存地址空间,定义如下:

structatag_mem{

            UINT32nSize;/*sizeofthearea*/

            UINT32ulStart;/*physicalstartaddress*/

};

其中nSzie表示内存的总大小,ulStart为内存的起始物理地址,二者结合告诉内核系统可用的物理内存空间是哪些。

StructTagCmdline结构体的定义就更简单了,只是一个字符数组,初始长度为1,如下所示:

structTagCmdline{

           charcCmdLine[1];/*thisistheminimumsize*/

};

实际上命令行参数不可能只有一个字节,我们通常使用strcpy函数把命令行参数拷贝到cCmdLine地址处,在结尾附加一个字符串结束符’\0’,然后用strlen函数获得cCmdLine数组的实际长度(包括字符串结束符)。

常见的命令行参数如:

root=/dev/mtdblock2init=/linuxrcconsole=ttySAC0,115200mem=65536。

我们知道的是,Bootloader以标记列表的形式向内核传递的参数,大概有10种不同类型的Tag,而命令行参数只是其中的一种。

其它需要设置的Tag包括ATAG_RAMDISK、ATAG_INITRD等,此处不再详细介绍。

在我们的S-Boot中设置了ATAG_CORE,ATAG_MEM,ATAG_CMDLINE,ATAG_NONE四项。

其中CmdLine使用的是:

constchar*CmdLine="root=/dev/nfsnfsroot=192.168.1.249:

/home/hongwang/mkrootfs/rootfsip=192

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 解决方案 > 营销活动策划

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1