您好,  [请登录] [QQ登录]  [支付宝登录[免费注册]

商品分类

分享到: 百度搜藏 搜狐微博 新浪微博 腾讯微博 QQ收藏 人人网 Facebook Twitter

Arm Linux BOOTLOADER全程详解

发布日期:2011-05-18

    网上关于Linux的BOOTLOADER文章不少了,但是多数是vivi,blob等比较巨大的步调,读起来不太方便,编译出的文件也比较大,并且更多的是面向开辟用的引导代码,做成产品时还要淘汰,这肯定程度影响了开辟速率,对初学者学习开销也比较大,在此阐发一种大抵的BOOTLOADER,是在三星公司提供的2410 BOOTLOADER上轻微修改后的结果,编译出来的文件大小不超过跨过4k,渴望对大家有所资助. 

    1.几个告急的见解 

    COMPRESSED KERNEL and DECOMPRESSED KERNEL 

    压缩后的KERNEL,根据文档数据,如今不提倡利用DECOMPRESSED KERNEL,而要利用COMPRESSED KERNEL,它包括相识压器.因此要在ram分派时给压缩和解压的KERNEL提供富裕空间,如许它们不会相互包围.

    当实行指令跳转到COMPRESSED KERNEL后,解压器就开始变乱,要是解压器探测到解压的代码会包围失COMPRESSED KERNEL,那它会直接跳到COMPRESSED KERNEL后存放数据,并且重新定位KERNEL,以是要是没有富裕空间,就会堕落. 

    Jffs2 File System 

    可以使armlinux应用中孕育孕育产生的数据生存在FLASH上,我的板子还没用到这个. 

    RAMDISK 

    利用RAMDISK可以使ROOT FILE SYSTEM在没有其他配置的环境下启动.一样通常有两种加载要领,我就先容最常用的吧,把COMPRESSED RAMDISK IMAGE放到指定地点,然后由BOOTLOADER把这个地点通过启动参数的要领ATAG_INITRD2转达给KERNEL.细致看代码阐发. 

    启动参数(摘自IBM developer) 

    在调用内核之前,应该作一步准备变乱,即:设置 Linux 内核的启动参数。Linux 2.4.x 以后的内核都渴望以标记列表(tagged list)的情势来转达启动参数。启动参数标记列表以标记 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上有32M NOR FLASH,串口固然是逃不失的.如许,根据数据手册,地点分派如下:

0x4000_0000开始是4k的片内DRAM.

0x0000_0000开始是32M FLASH 16bit宽度

0x3000_0000开始是64M SDRAM 32bit宽度 

    过细:控制寄存器中的BANK6和BANK7部分必须雷同.

0x4000_0000(片内DRAM)存放4k以内的BOOTLOADER IMAGE

0x3000_0100开始存放启动参数

0x3120_0000 存放COMPRESSED KERNEL IMAGE

0x3200_0000 存放COMPRESSED RAMDISK

0x3000_8000 指定为DECOMPRESSED KERNEL IMAGE ADDRESS

0x3040_0000 指定为DECOMPRESSED RAMDISK IMAGE ADDRESS 

    开辟环境:Redhat Linux,armgcc toolchain, armlinux KERNEL 

    怎样创建armgcc的编译环境:提倡利用toolchain,而不要本身去编译armgcc,偶试过很多多少次,都以失败告终. 

    先下载arm-gcc 3.3.2 toolchain 

    将arm-linux-gcc-3.3.2.tar.bz2 解压到 /toolchain 

    # tar jxvf arm-linux-gcc-3.3.2.tar.bz2 

    # mv /usr/local/arm/3.3.2 /toolchain 

    在makefile 中在把arch=arm CROSS_COMPILE设置成toolchain的路径 

    另有便是INCLUDE = -I ../include -I /root/my/usr/local/arm/3.3.2/include.,不然库函数就不克不及用了 

    3.启动要领: 

    可以放在FLASH里启动,大概用Jtag仿真器.由于利用NOR FLASH,根据2410的手册,片内的4K DRAM在不须要设置便可以直接利用,而其他存储器必须先初始化,比如报告memory controller,BANK6里有两块SDRAM,数据宽度是32bit,= =.不然memory control会根据复位后的默认值来处理惩罚处罚存储器.如许读写就会孕育孕育产生错误. 

    以是第一步,通过仿真器把实行代码放到0x4000_0000,(在编译的时间,设置TEXT_BAS

E=0x40000000) 

    第二步,通过 AxD把linux KERNEL IMAGE放到目标地点(SDRAM)中,等待调用 

    第三步,实行BOOTLOADER代码,从串口得到调试数据,引导armlinux 

    4.代码阐发 

    讲了那么多实行的步调,是想让大家对启动有个大概印象,接着便是BOOTLOADER内部的代码阐发了,BOOTLOADER文章内容网上很多,我这里精简了下,删除了不须要的结果. 

    BOOTLOADER一样通常分为2部分,汇编部分和c语言部分,汇编部分实行大抵的硬件初始化,C部分认真复制数据,设置启动参数,串口通讯等结果. 

    BOOTLOADER的生命周期:

1. 初始化硬件,比如设置UART(至少设置一个),检测存储器= =.

2. 设置启动参数,这是为了报告内核硬件的信息,比如用哪个启动界面,波特率 = =.

3. 跳转到Linux KERNEL的首地点.

4. 去世亡 

    固然,在引导阶段,象vivi等,都用虚地点,要是你嫌烦的话,就用实地点,都一样. 

    我们来看代码:

2410init.s

.global _start//开始实行处

_start:

//下面是克制向量

b reset @ Supervisor Mode//重新启动后的跳转

……

……

reset:

ldr r0,=WTCON /WTCON地点为53000000,watchdog的控制寄存器 */

ldr r1,=0x0 /*关watchdog*/

str r1,[r0]



ldr r0,=INTMSK

ldr r1,=0xffffffff /*屏蔽全部克制*/

str r1,[r0]



ldr r0,=INTSUBMSK

ldr r1,=0x3ff /*子克制也一样*/

str r1,[r0]

/*Initialize Ports...for display LED.*/

ldr r0, =GPFCON

ldr r1, =0x55aa

str r1, [r0]

ldr r0, =GPFUP

ldr r1, =0xff

str r1, [r0]

ldr r0,=GPFDAT

ldr r1,=POWEROFFLED1

str r1,[r0]

/* Setup clock Divider control register

* you must configure CLKDIVN before LOCKTIME or MPLL UPLL

* because default CLKDIVN 1,1,1 set the SDMRAM Timing Conflict

nop

* FCLK:HCLK:PCLK = 1:2:4 in this case

*/

ldr r0,=CLKDIVN

ldr r1,=0x3

str r1,[r0]



/*To reduce PLL lock time, adjust the LOCKTIME register. */

ldr r0,=LOCKTIME

ldr r1,=0xffffff

str r1,[r0]

/*Configure MPLL */

ldr r0,=MPLLCON

ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) //Fin=12MHz,Fout=203MHz

str r1,[r0]

ldr r1,=GSTATUS2

ldr r10,[r1]

tst r10,#OFFRST

bne 1000f

//以上这段,我没动,就用三星写的了,下面是紧张要改的地方

/* MEMORY C0NTROLLER(MC)设置*/

add r0,pc,#MCDATA - (.+8)// r0指向MCDATA地点,那边存放着MC初始化要用到的数据

ldr r1,=BWSCON // r1指向MC控制器寄存器的首地点

add r2,r0,#52 // 复制次数,偏移52字



1: //根据偏移量举行循环复制

ldr r3,[r0],#4

str r3,[r1],#4

cmp r2,r0

bne 1b

.align 2



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)

.word 0xB2 /* REFRESH Control Register */

.word 0x30 /* BANKSIZE Register : Burst Mode */

.word 0x30 /* SDRAM Mode Register */



.align 2

.global call_main //调用main函数,函数参数都为0

call_main:

ldr sp,STACK_START

mov fp,#0 /* no previous frame, so fp=0*/

mov a1, #0 /* set argc to 0*/

mov a2, #0 /* set argv to NUL*/

bl main /* call main*/

STACK_START:

.word STACK_BASE

undefined_instruction:

software_interrupt:

prefetch_abort:

data_abort:

not_used:

irq:

fiq:

/*以上是紧张的汇编部分,实现了时钟设置,串口设置watchdog封闭,克制封闭结果(要是有须要还可以降频利用),然后转入main*/

2410init.c file

int main(int argc,char **argv)

{

u32 test = 0;

void (*theKERNEL)(int zero, int arch, unsigned long params_addr) = (void (*)(int, int, unsigned long))RAM_COMPRESSED_KERNEL_BASE; //压缩后的IMAGE地点

int i,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=12MHz FCLK=200MHz

Port_Init();//设置I/O端口,在利用com口前,必须调用这个函数,不然通讯芯片底子得不到数据

Uart_Init(PCLK, 115200);//PCLK利用默认的200000,拨特率115200

/*******************(查抄ram空间)*******************/

Uart_SendString("\n\tLinux S3C2410 Nor BOOTLOADER\n");

Uart_SendString("\n\tChecking SDRAM 2410loader.c...\n");

for(;chkBs<0x33FA0140;chkBs=chkBs+0x4,test++)//



//根据我的经历,最好以一个字节为递增,我们的板子,在256byte递增检测的时间是没标题标,但是

//以1byte递增就堕落了,第13跟数据线随几的会冒”1”,检测出来是硬件标题,征象如下

//用仿真器下代码测试SDRAM,开始没贴28F128A3J FLASH影戏,测试结果很好,但在上了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

goto error;

}

Uart_SendString("\n\tSDRAM Check Successful!\n\tMemory Maping...");

get_memory_map();

//得到可用memory 信息,做成列表,反面会作为启动参数传给KERNEL

//所谓内存映射便是指在4GB 物理地点空间中有哪些地点范畴被分派用来寻址体系的 RAM 单位。

Uart_SendString("\n\tMemory Map Successful!\n");

//我用仿真器把KERNEL,RAMDISK直接放在SDRAM上,以是下面这段是不须要的,但是要是KERNEL,RAMDISK在FLASH里,那就须要.

/*******************(copy linux KERNEL)*******************/

Uart_SendString("\tLoading KERNEL IMAGE from FLASH... \n ");

Uart_SendString("\tand copy KERNEL IMAGE to SDRAM at 0x31000000\n");

Uart_SendString("\t\tby LEIJUN DONG
dongleijun4000@hotmail.com 此邮件地点受spam bots掩护,须要利用 Javascript 结果来查阅。 \n");

for(k = 0;k < 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M

* (u32 *)downPt = * (u32 *)fromPt;

/*******************(load RAMDISK)*******************/

Uart_SendString("\t\tloading COMPRESSED RAMDISK...\n");

downPt=(RAM_COMPRESSED_RAMDISK_BASE);

fromPt=(FLASH_RAMDISK_BASE);

for(k = 0;k < 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M

* (u32 *)downPt = * (u32 *)fromPt;

/******jffs2文件体系,在开辟中要是用不到FLASH,这段也可以不要********/

Uart_SendString("\t\tloading jffs2...\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( "Load Success...Run...\n ");

/*******************(setup param)*******************/

setup_start_tag();//开始设置启动参数

setup_memory_tags();//内存印象

setup_commandline_tag("console=ttyS0,115200n8");//启动下令行

setup_initrd2_tag();//root device

setup_RAMDISK_tag();//ramdisk image

setup_end_tag();

/*关I-cache */

asm ("mrc p15, 0, %0, c1, c0, 0": "=r" (i));

i &= ~0x1000;

asm ("mcr p15, 0, %0, c1, c0, 0": : "r" (i));

/* flush I-cache */

asm ("mcr p15, 0, %0, c7, c5, 0": : "r" (i));

//下面这行就跳到了COMPRESSED KERNEL的首地点

theKERNEL(0, ARCH_NUMBER, (unsigned long *)(RAM_BOOT_PARAMS));

//启动kernel时间,I-cache可以开也可以关,r0必须是0,r1必须是CPU型号

(可以从linux/arch/arm/tools/mach-types中找到),r2必须是参数的物理开始地点

/*******************END*******************/

error:

Uart_SendString("\n\nPanic SDRAM check error!\n");

return 0;

}

static void setup_start_tag(void)

{

params = (struct tag *)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);

}





static void setup_memory_tags(void)

{

int i;



for(i = 0; i < NUM_MEM_AREAS; i++) {

if(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);

}

}

}





static void setup_commandline_tag(char *commandline)

{

int i = 0;

/* skip non-existent command lines so the kernel will still

* use its default command line.

*/

params->hdr.tag = ATAG_CMDLINE;

params->hdr.size = 8;

//console=ttyS0,115200n8

strcpy(params->u.cmdline.cmdline, p);

params = tag_next(params);

}





static void setup_initrd2_tag(void)

{

/* an ATAG_INITRD node tells the kernel where the compressed

* ramdisk can be found. ATAG_RDIMG is a better name, actually.

*/

params->hdr.tag = ATAG_INITRD2;

params->hdr.size = tag_size(tag_initrd);

params->u.initrd.start = RAM_COMPRESSED_RAMDISK_BASE;

params->u.initrd.size = 2047;//k byte

params = tag_next(params);

}





static void setup_ramdisk_tag(void)

{

/* an ATAG_RAMDISK node tells the kernel how large the

* decompressed ramdisk will become.

*/

params->hdr.tag = ATAG_RAMDISK;

params->hdr.size = tag_size(tag_ramdisk);

params->u.ramdisk.start = RAM_DECOMPRESSED_RAMDISK_BASE;

params->u.ramdisk.size = 7.8*1024; //k byte

params->u.ramdisk.flags = 1; // automatically load ramdisk

params = tag_next(params);

}





static void setup_end_tag(void)

{

params->hdr.tag = ATAG_NONE;

params->hdr.size = 0;

} void Uart_Init(int pclk,int baud)//串口是很告急的

{

int i;

if(pclk == 0)

pclk = PCLK;

rUFCON0 = 0x0; //UART channel 0 FIFO control register, FIFO disable

rUMCON0 = 0x0; //UART chaneel 0 MODEM control register, AFC disable



//UART0

rULCON0 = 0x3; //Line control register : Normal,No parity,1 stop,8 bits

下面这段samsung好象写的不太对,但是我根据Normal,No parity,1 stop,8 bits算出来的确是0x245



// [10] [9] [8] [7] [6] [5] [4] [3:2] [1:0]

// Clock Sel, Tx Int, Rx Int, Rx Time Out, Rx err, Loop-back, Send break, Transmit Mode, Receive Mode

// 0 1 0 , 0 1 0 0 , 01 01

// PCLK Level Pulse Disable Generate Normal Normal Interrupt or Polling

rUCON0 = 0x245; // Control register

rUBRDIV0=( (int)(PCLK/16./ baud) -1 ); //Baud rate divisior register 0

delay(10);



    颠末以上的折腾,接下来便是kernel的活了.能不克不及启动kernel,得看你编译kernel的程度了. 

    这个BOOTLOADER不象blob那样须要交互信息,利用假造地点,总的来说非常简便明白.