出处: http://blog.chinaunix.net/uid-27229906-id-3313170.html
Freescale i.mx28 Boot-stream分析
成都莱得科技
一: 摘要
基于freescale的i.MX28处理器,ARM926EJ-S内核,最高主频400MHZ,内存DDR2 128MB,NAND FLASH 128MB,i.MX28有极高的集成度,可以降低系统总体成本,低功耗平台,能满足电池供电环境。内嵌L2 Switch,支持双网口;双CAN总线;多达6个UART,便于工控产品系统集成。平台软件丰富稳定,为降低客户设计要求,在linux操作系统之上封装了丰富的协议、基础库、应用程序框架和应用程序支撑层。无需熟悉底层繁琐的基础性,linux API,只需要会C++即可设计出完美的客户应用程序。工业级和汽车级环境,宽温度范围,高稳定性,非常适合于工业控制、汽车电子、医疗器械、仪器仪表。
imx28 启动模式提供了2种引导linux kernel 的方法
1:Boot-stream:
直接启动linux,使用imx-boolets 生成_linux.sb包含了硬件初始化和kernel引导代码。Linux_prep阶段将内核启动参数传递给kernel,然后跳到kernel (zImage) 处运行。
2:U-boot
对于i.MX28,U-Boot是用于在网络上的Linux内核映像加载到SDRAM,因为i.MX28内置ROM固件中没有实现的TCP / IP网络协议栈。i.MX28 U-Boot实现了FEC以太网控制器内置的驱动程序,可以使用TCP / IP网络下载kernel. (NFS,TFTP)
本文的主角是Boot-stream,所以U-boot就此飘过.......
二:启动流程
Boot stream 启动经历四个阶段; 这里直接引用IMX28 USER'S GUIDE 文档的说明:
power_prep ― This bootlet configures the power supply.
boot_prep ― This bootlet configures the clocks and SDRAM.
linux_prep ― This bootlet prepares to boot Linux
三:代码分析
(1)power_prep
国际惯例,先来查看linker script,
在./imx-bootlets-src/power_prep目录下:link.lds文件:
点击(此处)折叠或打开
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
}
OUTPUT_ARCH(arm):;指定输出可执行文件的平台为ARM
=号前的"."为地址计数器(location counter ),该符号只能用于SECTIONS命令内部,初始值为‘0’,可以对该符号进行赋值,也可以使用该符号进行计算或赋值给其他符号。它会自动根据SECTIONS命令内部所描述的输出段的大小来计算当前的地址。
可以看出,代码的执行地址和存储地址就是0x00000000,以4字节对齐方式存储,依次存放text,data,bbs段。
ENTRY(_start)指定代码执行的入为_start。
进入imx-bootlets-src/power_prep/power_prep.c
_start:对寄存器的设置
PowerPrep_CPUClock2XTAL();
//设置时钟为外部时钟源。
PowerPrep_ClearAutoRestart();
//clear RTC ALARM wakeup or AUTORESTART bits here
hw_power_SetPowerClkGate( false );
//空函数,采用默认的设置
HW_POWER_VDDDCTRL.B.LINREG_OFFSET= HW_POWER_LINREG_OFFSET_STEP_BELOW;
HW_POWER_VDDACTRL.B.LINREG_OFFSET =
HW_POWER_LINREG_OFFSET_STEP_BELOW;
HW_POWER_VDDIOCTRL.B.LINREG_OFFSET =
HW_POWER_LINREG_OFFSET_STEP_BELOW;
//如果没在这个范围,将产生一个FIQ,可以参照 i.MX28 Datasheet 在不同频率时设置这个掉电
//电压。即设置电源的波动范围
/*关于VDDD VDDA VDDIO,这里引用数据手册的解释:
VDDD―provides power to the digital components of the i.MX28 processor. The system clocks
use the VDDD power rail.
VDDA―provides power specifically to the audio systems, headphone amp. The VDDA power rail
also provides power to regulate the VDDMEM rail.
VDDIO―provides power to the I/O peripherals and also to the NAND Flash and external Secure
Digital/MultiMedia Cards (SD/MMC).
*/
PowerPrep_Setup5vDetect();
PowerPrep_SetupBattDetect();
//设置电源探测的初始化。
PowerPrep_ConfigurePowerSource();
//探测可用的电源:
//内部电池或者外部充电电源。
//如果探测到可用的电池,就将电池作为供电电源,否则用外部电池。
//但是电池需要开发板的支持,IMX28 EVK没自带电池。
//探测过程的PMU寄存器的设置繁琐的"令人发指"。但只要依据数据手册的设置就没问
//题了。
PowerPrep_EnableOutputRailProtection();
//开启了三路电源vddio vdda vddd的browout的中断,但是如果在插入SD卡时候,导//致板子重
//启,那是因为插入SD卡,VDDIO负载变大,导致出现电压变化 。可以在
//PowerPrep_EnableOutputRailProtection()中禁止掉VDDIO的browout中断。
ddi_power_SetVddio(3300, 3150);
ddi_power_SetVddd(1350, 1200);
//设置VDDIO和VDDD的值和brownout level 。
HW_POWER_CTRL_CLR(
BM_POWER_CTRL_VDDD_BO_IRQ |
BM_POWER_CTRL_VDDA_BO_IRQ |
BM_POWER_CTRL_VDDIO_BO_IRQ |
BM_POWER_CTRL_VDD5V_DROOP_IRQ |
BM_POWER_CTRL_VBUSVALID_IRQ |
BM_POWER_CTRL_BATT_BO_IRQ |
BM_POWER_CTRL_DCDC4P2_BO_IRQ
);
//清除不必要中断。
if (!bBatteryReady)
HW_POWER_5VCTRL_SET(BM_POWER_5VCTRL_PWDN_5VBRNOUT);
//如果电池没输入,就自动关闭电池电压。注意只是关闭电池。如果外部电源有输入,
//系统启动没影响。
return iRtn;
(2) boot_prep
依旧查看imx-bootlets-src/boot_prep 目录下的link.lds
点击(此处)折叠或打开
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text : { *(.text) }
.data : { *(.data) }
.bss : { *(.bss) }
}
link.lds和powe_prep下的link.lds一样,毫无压力,你们懂得。
代码到此将进入boot_prep的_start()
过程很简单,结果很完美。
1:设置RAM为DDR2模式
2:设置CPU CLK
3:设置DDR2 的频率为166MHZ
4:测试RAM
设置完全就是寄存器的配置。这里就不一一分析了,参照数据手册很好就明白了。
这里测试RAM的方法很简单,
volatile int *pTest = 0x40000000;
向0x40000000写入1000个数,在读出来,没有错误就说明RAM正常。
(3) linux_prep
向kernel传递正确的参数,引导linux kernel。设置好标记列表后就要调用内核了。但调用内核前,CPU寄存器的设置必须满足下面的条件:
r0=0
r1=机器码
r2=内核参数标记列表在RAM中的起始地址
imx-bootlets-src/linux_prep/core/entry.S 承载着这一使命。
_start:
stmdbsp!, {r4-r12, lr}
/* Check entry counter */
ldrr4, entry_count
cmpr4, #0
bnestart_kernel
//如果从休眠启动内核,entry_count > 0跳到try_to_resume;
//否则entry_count = 0;直接start_kernel
bltry_to_resume
/* Update counter to show we were here */
addr4, r4, #1
strr4, entry_count
//增加entry_count,用以标示内核从什么状态启动。
/* Return to ROM */
ldmiasp!, {r4-r12, lr}
bxlr
//在来看看start_kernel 做了哪些工作呢?
start_kernel:
blclear_bss
//首选清除BBS段,如果不清除,以后程序会出现执行错误
/* Initialize HW modules relevant for linux_prep */
blhw_init
/* Setup tags for Linux kernel and save tags pointer in r2 */
blsetup_tags
movr2, r0
//R2里面保持了内核启动参数的地址, r0里面存储的是setup_tags()的返回值。
//即内核启动参数的地址
//setup_tags()函数式用C语言编写,设置启动内核的参数
/* Store machine id in r1 */
ldrr1, =MACHINE_ID
//R1保持了MX28_EVK的ID
//其实就是个数字 #define MACHINE_ID2531
/* Zero r0 register */
movr0, #0
//当然要清除R0了,这个启动内核前的需求。
/* Jump to Linux kernel */
ldrlr, =KERNEL_BASE_ADDRESS
movpc, lr
//跳到内核代码处,将执行内核代码的自解压程序
//如果从休眠状态启动内核,就跳到 try_to_resume .
设置启动内核的参数
u32 setup_tags (void)
{
enum magic_key magic_key;
magic_key = get_magic_key();
find_command_lines();
setup_start_tag();
setup_mem_tag();
setup_initrd_tag();
setup_cmdline_tag(cmdlines[magic_key]);
setup_end_tag();
return (u32)ATAGS_BASE_ADDRESS;
}
(1)setup_start_tag函数
static void setup_start_tag (bd_t *bd)
{
params = (struct tag*)ATAGS_BASE_ADDRESS; /* 内核的参数的开始地址 */
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);
}
标记列表必须以ATAG_CORE开始,setup_start_tag函数在内核的参数的开始地址设置了一个ATAG_CORE标记。
(2)setup_memory_tags函数
static void setup_memory_tags (bd_t *bd)
{
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size(tag_mem32);
params->u.mem.start = SDRAM_BASE;
params->u.mem.size = get_sdram_size();
params = tag_next(params);
}
setup_memory_tags函数设置了一个ATAG_MEM标记,该标记包含内存起始地址,内存大小这两个参数。
(3)setup_initrd_tag函数
static void setup_initrd_tag(void)
{
params->hdr.tag = ATAG_INITRD2 ;
params->hdr.size = tag_size(tag_initrd);
params->u.initrd.start = 0x40400000;
params->u.initrd.size = 0x00400000;
params = tag_next(params);
}
设置了 ramdisk image 的起始物理地址和大小。
(4)setup_end_tag函数
static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
标记列表必须以标记ATAG_NONE结束,setup_end_tag函数设置了一个ATAG_NONE标记,表示标记列表的结束。
ldrlr, =KERNEL_BASE_ADDRESS
movpc, lr
接下来,将KERNEL_BASE_ADDRESS 传递给PC指针,将代码控制权交给linux kernel。Boot-stream 精彩的生命到此结束了,留下了OS的各种膜拜和感激,让OS去走它自己的路吧。相信它会走的更好。
四:IMX28-EVK编译bootlets.
直接启动linux 这种方法需要将生产的zImage镜像制成imx28_linux.sb镜像imx28_linux.sb镜像是对zImage镜像做了些加工,为zImage的运行准备环境。
在编译boot-stream前假定已经安装好了整个开发环境(ltib).如果没有安装,请参照宁一篇文章 《Freescale Imx28 开发环境的搭建 》
#mkdir boot-stream
#cd boot-stream
#sudo tar xvfz /opt/freescale/pkgs/imx-bootlets-src
# cp ~/linux/arch/arm/boot/zImage ./imx-bootlets-src/
#make CROSS_COMPILE=
/opt/freescale/usr/local/gcc-4.1.2-glibc-2.5-nptl-3/arm-none-linux-gnueabi/bin/arm-none- linux-gnueabi-
BOARD=iMX28_EVK
注意,这里可能会出现如下错误
elftosb2 -z -c ./linux_prebuilt.db -o imx28_linux.sb
make: elftosb2: Command not found
可能是环境设置不对,在这里为了图方便,直接cp /opt/freescale/ltib/usr/bin/elftosb2
到 imx-bootlets-src下。当然你也可以用export 导出环境变量。
编译完成后将生成imx28-linux.sb。这个就是将要烧写的linux。
然后插入SD卡,利用ltib提供的mk_hdr.sh 烧写进SDCARD就行.
#cp imx28-linnux.sb ~/ltib/rootfs/boot
#cd ~/ltib
#./mk_hdr.sh /dev/sdx //sdx插入的SDCARD,sdb. 在烧写前,需要先umount SD卡。
烧写完成将SDCARD插入到IMX28-EVK的socket 0 。按下power,启动linux。Enjoy it!!!
到这里boot-stream 的讲解也ALT+F4了,不过探索linux的路才刚刚起步