Linux BOOTLOADER全程详解.docx
《Linux BOOTLOADER全程详解.docx》由会员分享,可在线阅读,更多相关《Linux BOOTLOADER全程详解.docx(16页珍藏版)》请在冰豆网上搜索。
LinuxBOOTLOADER全程详解
LinuxBOOTLOADER全程详解(ArmS3C2410)
网上关于Linux的BOOTLOADER文章不少了,但是大都是vivi,blob等比较庞大的程序,读起来不太方便,编译出的文件也比较大,而且更多的是面向开发用的引导代码,做成产品时还要裁减,这一定程度影响了开发速度,对初学者学习开销也比较大,在此分析一种简单的BOOTLOADER,是在三星公司提供的2410BOOTLOADER上稍微修改后的结果,编译出来的文件大小不超过4k,希望对大家有所帮助.
1.几个重要的概念
COMPRESSEDKERNELandDECOMPRESSEDKERNEL
压缩后的KERNEL,按照文档资料,现在不提倡使用DECOMPRESSEDKERNEL,而要使用COMPRESSEDKERNEL,它包括了解压器.因此要在ram分配时给压缩和解压的KERNEL提供足够空间,这样它们不会相互覆盖.
当执行指令跳转到COMPRESSEDKERNEL后,解压器就开始工作,如果解压器探测到解压的代码会覆盖掉COMPRESSEDKERNEL,那它会直接跳到COMPRESSEDKERNEL后存放数据,并且重新定位KERNEL,所以如果没有足够空间,就会出错.
Jffs2FileSystem
可以使armlinux应用中产生的数据保存在FLASH上,我的板子还没用到这个.
RAMDISK
使用RAMDISK可以使ROOTFILESYSTEM在没有其他设备的情况下启动.一般有两种加载方式,我就介绍最常用的吧,把COMPRESSEDRAMDISKIMAGE放到指定地址,然后由BOOTLOADER把这个地址通过启动参数的方式ATAG_INITRD2传递给KERNEL.具体看代码分析.
启动参数(摘自IBMdeveloper)
在调用内核之前,应该作一步准备工作,即:
设置Linux内核的启动参数。
Linux2.4.x以后的内核都期望以标记列表(taggedlist)的形式来传递启动参数。
启动参数标记列表以标记ATAG_CORE开始,以标记ATAG_NONE结束。
每个标记由标识被传递参数的tag_header结构以及随后的参数值数据结构来组成。
数据结构tag和tag_header定义在Linux内核源码的include/asm/setup.h头文件中.
在嵌入式Linux系统中,通常需要由BOOTLOADER设置的常见启动参数有:
ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。
(注)参数也可以用COMMANDLINE来设定,在我的BOOTLOADER里,我两种都用了.
2.开发环境和开发板配置:
CPU:
S3C2410,BANK6上有64M的SDRAM(两块),BANK0上有32MNORFLASH,串口当然是逃不掉的.这样,按照数据手册,地址分配如下:
0x4000_0000开始是4k的片内DRAM.
0x0000_0000开始是32MFLASH16bit宽度
0x3000_0000开始是64MSDRAM32bit宽度
注意:
控制寄存器中的BANK6和BANK7部分必须相同.
0x4000_0000(片内DRAM)存放4k以内的BOOTLOADERIMAGE
0x3000_0100开始存放启动参数
0x3120_0000存放COMPRESSEDKERNELIMAGE
0x3200_0000存放COMPRESSEDRAMDISK
0x3000_8000指定为DECOMPRESSEDKERNELIMAGEADDRESS
0x3040_0000指定为DECOMPRESSEDRAMDISKIMAGEADDRESS
开发环境:
RedhatLinux,armgcctoolchain,armlinuxKERNEL
如何建立armgcc的编译环境:
建议使用toolchain,而不要自己去编译armgcc,偶试过好多次,都以失败告终.
先下载arm-gcc3.3.2toolchain
将arm-linux-gcc-3.3.2.tar.bz2解压到/toolchain
#tarjxvfarm-linux-gcc-3.3.2.tar.bz2
#mv/usr/local/arm/3.3.2/toolchain
在makefile中在把arch=armCROSS_COMPILE设置成toolchain的路径
还有就是INCLUDE=-I../include-I/root/my/usr/local/arm/3.3.2/include.,否则库函数就不能用了
3.启动方式:
可以放在FLASH里启动,或者用Jtag仿真器.由于使用NORFLASH,根据2410的手册,片内的4KDRAM在不需要设置便可以直接使用,而其他存储器必须先初始化,比如告诉memorycontroller,BANK6里有两块SDRAM,数据宽度是32bit,==.否则memorycontrol会按照复位后的默认值来处理存储器.这样读写就会产生错误.
所以第一步,通过仿真器把执行代码放到0x4000_0000,(在编译的时候,设定TEXT_BAS
E=0x40000000)
第二步,通过AxD把linuxKERNELIMAGE放到目标地址(SDRAM)中,等待调用
第三步,执行BOOTLOADER代码,从串口得到调试数据,引导armlinux
4.代码分析
讲了那么多执行的步骤,是想让大家对启动有个大概印象,接着就是BOOTLOADER内部的代码分析了,BOOTLOADER文章内容网上很多,我这里精简了下,删除了不必要的功能.
BOOTLOADER一般分为2部分,汇编部分和c语言部分,汇编部分执行简单的硬件初始化,C部分负责复制数据,设置启动参数,串口通信等功能.
BOOTLOADER的生命周期:
1.初始化硬件,比如设置UART(至少设置一个),检测存储器==.
2.设置启动参数,这是为了告诉内核硬件的信息,比如用哪个启动界面,波特率==.
3.跳转到LinuxKERNEL的首地址.
4.消亡
当然,在引导阶段,象vivi等,都用虚地址,如果你嫌烦的话,就用实地址,都一样.
我们来看代码:
2410init.s
.global_start//开始执行处
_start:
//下面是中断向量
breset@SupervisorMode//重新启动后的跳转
……
……
reset:
ldrr0,=WTCON/WTCON地址为53000000,watchdog的控制寄存器*/
ldrr1,=0x0/*关watchdog*/
strr1,[r0]
ldrr0,=INTMSK
ldrr1,=0xffffffff/*屏蔽所有中断*/
strr1,[r0]
ldrr0,=INTSUBMSK
ldrr1,=0x3ff/*子中断也一样*/
strr1,[r0]
/*InitializePorts...fordisplayLED.*/
ldrr0,=GPFCON
ldrr1,=0x55aa
strr1,[r0]
ldrr0,=GPFUP
ldrr1,=0xff
strr1,[r0]
ldrr0,=GPFDAT
ldrr1,=POWEROFFLED1
strr1,[r0]
/*SetupclockDividercontrolregister
*youmustconfigureCLKDIVNbeforeLOCKTIMEorMPLLUPLL
*becausedefaultCLKDIVN1,1,1settheSDMRAMTimingConflict
nop
*FCLK:
HCLK:
PCLK=1:
2:
4inthiscase
*/
ldrr0,=CLKDIVN
ldrr1,=0x3
strr1,[r0]
/*ToreducePLLlocktime,adjusttheLOCKTIMEregister.*/
ldrr0,=LOCKTIME
ldrr1,=0xffffff
strr1,[r0]
/*ConfigureMPLL*/
ldrr0,=MPLLCON
ldrr1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV)//Fin=12MHz,Fout=203MHz
strr1,[r0]
ldrr1,=GSTATUS2
ldrr10,[r1]
tstr10,#OFFRST
bne1000f
//以上这段,我没动,就用三星写的了,下面是主要要改的地方
/*MEMORYC0NTROLLER(MC)设置*/
addr0,pc,#MCDATA-(.+8)//r0指向MCDATA地址,那里存放着MC初始化要用到的数据
ldrr1,=BWSCON//r1指向MC控制器寄存器的首地址
addr2,r0,#52//复制次数,偏移52字
1:
//按照偏移量进行循环复制
ldrr3,[r0],#4
strr3,[r1],#4
cmpr2,r0
bne1b
.align2
MCDATA:
.word(0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
上面这行就是BWSCON的数据,具体参数意义如下:
需要更改设置DW6和DW7都设置成10,即32bit,DW0设置成01,即16bit
下面都是每个BANK的控制器数据,大都是时钟相关,可以用默认值,设置完MC后,就跳到调用main函数的部分
.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)
.word0xB2/*REFRESHControlRegister*/
.word0x30/*BANKSIZERegister:
BurstMode*/
.word0x30/*SDRAMModeRegister*/
.align2
.globalcall_main//调用main函数,函数参数都为0
call_main:
ldrsp,STACK_START
movfp,#0/*nopreviousframe,sofp=0*/
mova1,#0/*setargcto0*/
mova2,#0/*setargvtoNUL*/
blmain/*callmain*/
STACK_START:
.wordSTACK_BASE
undefined_instruction:
software_interrupt:
prefetch_abort:
data_abort:
not_used:
irq:
fiq:
/*以上是主要的汇编部分,实现了时钟设置,串口设置watchdog关闭,中断关闭功能(如果有需要还可以降频使用),然后转入main*/
2410init.cfile
intmain(intargc,char**argv)
{
u32test=0;
void(*theKERNEL)(intzero,intarch,unsignedlongparams_addr)=(void(*)(int,int,unsignedlong))RAM_COMPRESSED_KERNEL_BASE;//压缩后的IMAGE地址
inti,k=0;
//downPt=(RAM_COMPRESSED_KERNEL_BASE);
chkBs=(_RAM_STARTADDRESS);//SDRAM开始的地方
//fromPt=(FLASH_LINUXKERNEL);
MMU_EnableICache();
ChangeClockDivider(1,1);//1:
2:
4
ChangeMPllvalue(M_MDIV,M_PDIV,M_SDIV);//Fin=12MHzFCLK=200MHz
Port_Init();//设置I/O端口,在使用com口前,必须调用这个函数,否则通信芯片根本得不到数据
Uart_Init(PCLK,115200);//PCLK使用默认的200000,拨特率115200
/*******************(检查ram空间)*******************/
Uart_SendString("\n\tLinuxS3C2410NorBOOTLOADER\n");
Uart_SendString("\n\tCheckingSDRAM2410loader.c...\n");
for(;chkBs<0x33FA0140;chkBs=chkBs+0x4,test++)//
//根据我的经验,最好以一个字节为递增,我们的板子,在256byte递增检测的时候是没问题的,但是
//以1byte递增就出错了,第13跟数据线随几的会冒”1”,检测出来是硬件问题,现象如下
//用仿真器下代码测试SDRAM,开始没贴28F128A3JFLASH片子,测试结果很好,但在上了FLASH片子//之后,测试数据(data)为0x00000400连续成批写入读出时,操作大约1k左右内存空间就会出错,//而且随机。
那个出错数据总是变为0x00002400,数据总线10位和13位又没短路发生。
用其他数据//测试比如0x00000200;0x00000800没这问题。
dx帮忙。
//至今没有解决,所以我用不了Flash.
{
chkPt1=chkBs;
*(u32*)chkPt1=test;//写数据
if(*(u32*)chkPt1==1024))//读数据和写入的是否一样?
{
chkPt1+=4;
Led_Display
(1);
Led_Display
(2);
Led_Display(3);
Led_Display(4);
}
else
gotoerror;
}
Uart_SendString("\n\tSDRAMCheckSuccessful!
\n\tMemoryMaping...");
get_memory_map();
//获得可用memory信息,做成列表,后面会作为启动参数传给KERNEL
//所谓内存映射就是指在4GB物理地址空间中有哪些地址范围被分配用来寻址系统的RAM单元。
Uart_SendString("\n\tMemoryMapSuccessful!
\n");
//我用仿真器把KERNEL,RAMDISK直接放在SDRAM上,所以下面这段是不需要的,但是如果KERNEL,RAMDISK在FLASH里,那就需要.
/*******************(copylinuxKERNEL)*******************/
Uart_SendString("\tLoadingKERNELIMAGEfromFLASH...\n");
Uart_SendString("\tandcopyKERNELIMAGEtoSDRAMat0x31000000\n");
Uart_SendString("\t\tbyLEIJUNDONGdongleijun4000@\n");
for(k=0;k<196608;k++,downPt+=1,fromPt+=1)//3*1024*1024/32linuxKERNELdes,src,length=3M
*(u32*)downPt=*(u32*)fromPt;
/*******************(loadRAMDISK)*******************/
Uart_SendString("\t\tloadingCOMPRESSEDRAMDISK...\n");
downPt=(RAM_COMPRESSED_RAMDISK_BASE);
fromPt=(FLASH_RAMDISK_BASE);
for(k=0;k<196608;k++,downPt+=1,fromPt+=1)//3*1024*1024/32linuxKERNELdes,src,length=3M
*(u32*)downPt=*(u32*)fromPt;
/******jffs2文件系统,在开发中如果用不到FLASH,这段也可以不要********/
Uart_SendString("\t\tloadingjffs2...\n");
downPt=(RAM_JFFS2);
fromPt=(FLASH_JFFS2);
for(k=0;k<(1024*1024/32);k++,downPt+=1,fromPt+=1)
*(u32*)downPt=*(u32*)fromPt;
Uart_SendString("LoadSuccess...Run...\n");
/*******************(setupparam)*******************/
setup_start_tag();//开始设置启动参数
setup_memory_tags();//内存印象
setup_commandline_tag("console=ttyS0,115200n8");//启动命令行
setup_initrd2_tag();//rootdevice
setup_RAMDISK_tag();//ramdiskimage
setup_end_tag();
/*关I-cache*/
asm("mrcp15,0,%0,c1,c0,0":
"=r"(i));
i&=~0x1000;
asm("mcrp15,0,%0,c1,c0,0":
:
"r"(i));
/*flushI-cache*/
asm("mcrp15,0,%0,c7,c5,0":
:
"r"(i));
//下面这行就跳到了COMPRESSEDKERNEL的首地址
theKERNEL(0,ARCH_NUMBER,(unsignedlong*)(RAM_BOOT_PARAMS));
//启动kernel时候,I-cache可以开也可以关,r0必须是0,r1必须是CPU型号
(可以从linux/arch/arm/tools/mach-types中找到),r2必须是参数的物理开始地址
/*******************END*******************/
error:
Uart_SendString("\n\nPanicSDRAMcheckerror!
\n");
return0;
}
staticvoidsetup_start_tag(void)
{
params=(structtag*)RAM_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);
}
staticvoidsetup_memory_tags(void)
{
inti;
for(i=0;iif(memory_map[i].used){
params->hdr.tag=ATAG_MEM;
params->hdr.size=tag_size(tag_mem32);
params->u.mem.start=memory_map[i].start;
params->u.mem.size=memory_map[i].len;
params=tag_next(params);
}
}
}
staticvoidsetup_commandline_tag(char*commandline)
{
inti=0;
/*skipnon-existentcommandlinessothekernelwillstill
*useitsdefaultcommandl