Chinaunix首页 | 论坛 | 博客
  • 博客访问: 104955
  • 博文数量: 50
  • 博客积分: 3120
  • 博客等级: 中校
  • 技术积分: 800
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-25 16:40
文章存档

2011年(1)

2009年(9)

2008年(40)

我的朋友

分类: LINUX

2009-01-04 16:27:10

Arm Linux BOOTLOADER全程详解
网上关于LinuxBOOTLOADER文章不少了, 但是大都是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 结构以及随后的参数值数据结构来组成. 数据结构tagtag_header定义在 Linux 内核源码的include/asm/setup. h头文件中.  

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

()参数也可以用COMMANDLINE来设定,  在我的BOOTLOADER,  我两种都用了.  

2. 开发环境和开发板配置:

CPU:S3C2410, BANK6上有64MSDRAM(两块),  BANK0上有32M NOR FLASH,  串口当然是逃不掉的.  这样,  按照数据手册,  地址分配如下:

0x4000_0000开始是4k的片内DRAM.  
0x0000_0000
开始是32M FLASH 16bit宽度
0x3000_0000
开始是64M SDRAM 32bit宽度

注意:控制寄存器中的BANK6BANK7部分必须相同.  

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 Linuxarmgcc toolchainarmlinux 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_BASE = 0x40000000)

第二步, 通过 AxDlinux 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 Conflictnop
* 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("ntLinux S3C2410 Nor BOOTLOADERn");
Uart_SendString("ntChecking 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位又没短路 发生. 用其他数据//测试比如0x000002000x00000800没这问题. 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("ntSDRAM Check Successful!ntMemory Maping. . . ");
get_memory_map();
//
获得可用memory 信息, 做成列表, 后面会作为启动参数传给KERNEL
/*
所谓内存映射就是指在4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元. */

Uart_SendString("ntMemory Map Successful!n");
/*
我用仿真器把KERNEL, RAMDISK直接放在SDRAM, 所以下面这段是不需要的, 但是如果KERNEL, RAMDISKFLASH, 那就需要. */

 

/*******************(copy linux KERNEL)*******************/
Uart_SendString("tLoading KERNEL IMAGE from FLASH. . .  n ");
Uart_SendString("tand copy KERNEL IMAGE to SDRAM at 0x31000000n");
Uart_SendString("ttby LEIJUN DONG dongleijun4000@hotmail. com 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("ttloading 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("ttloading 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("nnPanic 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的水平了.

阅读(451) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~