ARM指令集:
ADC 带进位的32位数加法
ADD 32位数相加
AND 32位数的逻辑与
B 在32M空间内的相对跳转指令
BIC 32位数的逻辑位清零
BKPT 断点指令
BL 带链接的相对跳转指令
BLX 带链接的切换跳转
BX 切换跳转
CDP\CDP2 协处理器数据处理操作
CLZ 零计数
CMN 比较两个数的相反数
CMP 32位数比较
EOR 32位逻辑异或
LDC\LDC2 从协处理器取一个或多个32位值
LDM 从内存送多个32位字到ARM寄存器
LDR 从虚拟地址取一个单个的32位值
MCR\MCR2\MCRR 从寄存器送数据到协处理器
MLA 32位乘累加
MOV 传送一个32位数到寄存器
MRC\MRC2\MRRC 从协处理器传送数据到寄存器
MRS 把状态寄存器的值送到通用寄存器
MSR 把通用寄存器的值传送到状态寄存器
MUL 32位乘
MVN 把一个32位数的逻辑“非”送到寄存器
ORR 32位逻辑或
PLD 预装载提示指令
QADD 有符号32位饱和加
QDADD 有符号双32位饱和加
QSUB 有符号32位饱和减
QDSUB 有符号双32位饱和减
RSB 逆向32位减法
RSC 带进位的逆向32法减法
SBC 带进位的32位减法
SMLAxy 有符号乘累加(16位*16位)+32位=32位
SMLAL 64位有符号乘累加((32位*32位)+64位=64位)
SMALxy 64位有符号乘累加((32位*32位)+64位=64位)
SMLAWy 32位有号乘累加((32位*16位)>>16位)+32位=32位
SMULL 64位有符号乘累加(32位*32位)=64位
SMULxy 32位有符号乘(16位*16位=32位)
SMULWy 32位有符号乘(32位*16位>>16位=32位)
STC\STC2 从协处理器中把一个或多个32位值存到内存
STM 把多个32位的寄存器值存放到内存
STR 把寄存器的值存到一个内存的虚地址内间
SUB 32位减法
SWI 软中断
SWP 把一个字或者一个字节和一个寄存器值交换
TEQ 等值测试
TST 位测试
UMLAL 64位无符号乘累加((32位*32位)+64位=64位)
UMULL 64位无符号乘累加(32位*32位)=64位
无论是体系结构还是指令集,大家或多或少都应该对X86汇编有些了解,而对于嵌入式领域已被广泛采用的ARM 处理器,了解的可能并不多。如果你有兴趣从事嵌入式方面的开发,那么了解一些RISC 体系结构和ARM汇编的知识还是有必要的。这里,我们找出了这两种体系结构最明显的不同之处,并对此进行介绍,让大家对于RISC体系结构的汇编有一个基本的了解。首先,我们就来看一看基于RISC的ARM的体系结构。
当然,作为RISC 领域最成功的处理器,ARM也遵从上面的特点。这里,我们不妨来看一看在user 模式下,ARM处理器的体系结构,这对于我们了解其汇编语言是有好处的。而其它模式下只是有一些寄存器分组略有不同,大家可以在ARM的手册上查到。这里要说明的是,尽管ARM处理器也支持16位指令,不过在下文中,我们都假定ARM处理器在32 位模式下工作。
图:user模式下ARM处理器体系结构
从图中我们看到,在user 模式下,ARM CPU 有16个数据寄存器,被命名为r0~r15(这个要比x86的多一些)。r13~r15有特殊用途,其中:
◆ r13 - 指向当前栈顶,相当于x86的esp,这个东西在汇编指令中要用sp 表示
◆ r14 - 称作链接寄存器,指向函数的返回地址。用lr表示,这和x86将返回地址保存在栈中是不同的
◆ r15 - 类似于x86的eip,其值等于当前正在执行的指令的地址+8(因为在取址和执行之间多了一个译码的阶段),这个用pc表示
另外,ARM处理器还有一个名为cspr的寄存器,用来监视和控制内部操作,这点和x86 的状态寄存器是类似的。具体的内容就用到再说了。
ARM 指令集
ARM处理器可以支持3种指令集——ARM,Thumb和Jazelle。
采用那种指令集,由cspr中的标志位来决定。大体说来:
◆ ARM——这是ARM自身的32 位指令集
◆ Thumb ——这是一个全16 位的指令集,在16 位外部数据总线宽度下,这个指令集的效率要比32 位的ARM指令高一些。
◆ Jazelle ——这是一个8位指令集,用来加速Java字节码的执行
整个ARM指令集由数据处理指令、分支指令、Load-Store指令、程序中断指令和一些系统控制指令构成,除了Load-Store指令外,其他部分和x86指令集是比较类似的。但和x86相比,ARM指令最显著的特点它们都是32-bit 定长的。另外,由于arm是基于RISC指令集的,所以CPU只处理在寄存器中的数据并通过独立的load-store指令在内存和寄存器之间进行数据的传递。
在使用方面,ARM指令的格式也要比Intel的复杂些。一般说来,一条ARM指令有如下的形式:
其中:
* {S} —— 加上这个后缀的指令会更新cpsr 寄存器
* [Rd] —— 目的寄存器
* [Rn]/[Rm] —— 源寄存器
一般来说,arm 指令有3个操作数,其中Rm寄存器在执行指令前可以进入桶形移位器进行移位操作,而Rn则会直接进入ALU 单元。如果一条arm 指令只有2 个操作数,那么源寄存器按照Rm 来处理。例如,一条加法指令:
add r0, r1, #1
就会把r1+1的结果存放到r0中。
在熟悉了基本的汇编格式后,读者就可以自行去查询基本的ARM汇编指令了,下面,我们找出ARM中比较有特色部分——Load-Store指令结构,它是CPU 和内存进行通信的一个重要媒介。
Load-Store 指令体系
由于ARM CPU并不直接处理内存中的数据,这个指令体系就担起了在寄存器和内存之间交换数据的重要媒介。它要比x86 的内存访问机制复杂一些。该指令体系分成3 类:
◆ 单寄存器传输(这是与x86 最为相像的)
◆ 多寄存器传输
◆ 交换指令
单寄存器传输
先看第一个,很简单:把单一的数据传入(LDR) 或传出(STR)寄存器,对内存的访问可以是DWORD(32-bit), WORD(16-bit)和BYTE(8-bit)。指令的格式如下:
DWORD:
WORD:
BYTE:
addressing1 和addressing2 的分类下面再说,现在理解成某种寻址方式就可以了。
在单寄存器传输方面,还有以下三种变址模式,他们是:
◆ preindex
这种变址方式和x86的寻址机制是很类似的,先对寄存器进行运算,然后寻址,但是在寻之后,基址寄存器的内容并不发生改变,例如:
ldr r0, [r1, #4]
的含义就是把r1+4 这个地址处的DOWRD 加载到r0,而寻址后,r1 的内容并不改变。
◆ preindex with writeback
这种变址方式有点类似于++i的含义,寻址前先对基地址寄存器进行运算,然后寻址. 其基本的语法是在寻址符[]后面加上一个"!" 来表示.例如:
ldr r0, [r1, #4]!
就可以分解成:
add r1, r1, #4
ldr r0, [r1, #0]
◆ postindex
自然这种变址方式和i++的方式就很类似了,先利用基址寄存器进行寻址,然后对基址寄存器进行运算,其基本语法是把offset 部分放到[]外面,例如:
ldr r0, [r1], #4
就可以分解成:
ldr r0, [r1, #0]
add r1, r1, #4
如果你还记得x86 的SIB 操作的话,那么你一定想ARM是否也有,答案是有也没有。在ss上面提到的addressing1 和addressing2的区别就是比例寄存器的使用,addressing1可以使用[base, scale, 桶形移位器]来实现SB 的效果,或者通过[base,offset](这里的offset 可以是立即数或者寄存器)来实现SI 的效果,而addressing2则只能用后者了。于是每一种变址方式最多可以有3 种寻址方式,这样一来,最多可以有9种用来寻址的指令形式。例如:
ldr r0, [r1, r2, LSR #0x04]!
ldr r0, [r1, -#0x04]
ldr r0, [r1], LSR #0x04
每样找了一种,大概就是这个意思。到此,单寄存器传输就结束了,掌握这些足够应付差事了。下面来看看多寄存器传输吧。
多寄存器传输
说得很明白,意思就是通过一条指令同时把多个寄存器的内容写到内存或者从内存把数据写到寄存器中,效率高的代价是会增加系统的延迟,所以armcc 提供了一个编译器选项来控制寄存器的个数。指令的格式有些复杂:
我们先来搞明白寻址模式,多寄存器传输模式有4 种:
也就是说以A开头的都是在Rn的原地开始操作,而B开头的都是以Rn的下一个位置开始操作。如果你仍然感到困惑,我们不妨看个例子。
所有的示例指令执行前:
mem32[0x1000C] = 0x04
mem32[0x10008] = 0x03
mem32[0x10004] = 0x02
mem32[0x10000] = 0x01
r0 = 0x00010010
r1 = 0x00000000
r3 = 0x00000000
r4 = 0x00000000
1) ldmia r0!, {r1-r3} 2) ldmib r0!, {r1-r3}
执行后: 执行后:
r0 = 0x0010001C r0 = 0x0010001C
r1 = 0x01 r1 = 0x02
r2 = 0x02 r2 = 0x03
r3 = 0x03 r3 = 0x04
至于DA 和DB 的模式,和IA / IB 是类似的,不多说了。
最后要说的是,使用ldm 和stm指令对进行寄存器组的保护是很常见和有效的功能。配对方案:
stmia / ldmdb
stmib / ldmda
stmda / ldmib
stmdb / ldmia
继续来看两个例子:
执行前:
r0 = 0x00001000
r1 = 0x00000003
r2 = 0x00000002
r3 = 0x00000001
执行的指令:
stmib r0!, {r1-r3}
mov r1, #1 ; These regs have been modified
mov r2, #2
mov r3, #3
当前寄存器状态:
r0 = 0x0000100C
r1 = 0x00000001
r2 = 0x00000002
r3 = 0x00000003
ldmia r0!, {r1-r3}
最后的结果:
r0 = 0x00001000
r1 = 0x00000003
r2 = 0x00000002
r3 = 0x00000001
另外,我们还可以利用这个指令对完成内存块的高效copy:
loop
ldmia r9!, {r0-r7}
stmia r10!, {r0-r7}
cmp r9, r11
bne loop
说到这里,读者应该对RISC的Load-Store体系结构有一个大概的了解了,能够正确配对使用指令,是很重要的。
Linux BOOTLOADER全程详解
网上关于Linux的BOOTLOADER文章不少了,但是大都是vivi,blob等比较庞大的程序,读起来不太方便,编译出的文件也比较大,而且更多的是面向开发用的引导代码,做成产品时还要裁减,这一定程度影响了开发速度,对初学者学习开销也比较大,在此分析一种简单的BOOTLOADER,是在三星公司提供的2410BOOTLOADER上稍微修改后的结果,编译出来的文件大小不超过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_BASE=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 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位又没短路
发生。用其他数据//测试比如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("ntSDRAM Check Successful!ntMemory Maping...");
get_memory_map();
//获得可用memory 信息,做成列表,后面会作为启动参数传给KERNEL
//所谓内存映射就是指在4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。
Uart_SendString("ntMemory 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 0x31000000n"); Uart_SendString("ttby LEIJUN DONG [email protected] 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的水平了.