QNX相关历史文章:
这篇文章主要描述Initial program loader的相关内容,并以Freescale IXM6处理器为例讲解
1. Initial program loader
IPL的功能可以类比Uboot,IPL程序的任务是对硬件进行最低限度的配置,以创建一个startup程序运行的环境,至少包括以下内容:
- 从Reset异常向量开始执行;
- 配置内存控制器;
- 配置时钟;
- 设置堆栈,以便允许IPL库执行OS的验证和设置(下载、扫描、设置、跳转到OS镜像)
IPL的代码可能很简单,也可能非常复杂,这分别对应到Warm-start和Cold-start。在Warm-start中,已经有BIOS或者ROM monitor了,IPL需要做的工作就会少很多,而在Cold-start中,没有BIOS或者ROM monitor,因此需要实现全部的功能。
IPL的初始化部分是用汇编实现的(因为它从ROM执行,没有内存控制器),初始化硬件之后,IPL调用main()函数来初始化C语言环境。
设置好C语言环境后,IPL可以执行不同的任务,这个具体取决于操作系统是从linearly mapped设备启动,还是从bank-switched设备启动:
- linearly mapped,整个镜像在处理器的线性地址空间中,比如ROM;
- bank-switched,镜像不能完全由处理器寻址,比如Disk device、Network设备、串口或并口、以及bank-switched的ROM或RAM;
1.1 bank-switched
从bank-switched设备中启动时,需要以下几步:
- IPL必须调用函数与相关设备通信,比如串口下载时,IPL使用image_download_8250()函数,该函数用于配置和控制8250类串口控制器,完成设置后,该函数会将image拷贝到RAM中;
- IPL调用image_sacn()来扫描整个image,完成一些校验工作;
- IPL调用image_setup()来完成一些设置工作;
-
IPL调用image_start(),跳转到startup的起始地址,将控制权交给startup;
镜像加载时,由于是bank-switched,所以需要将整个镜像都拷贝到RAM中,如下图所示:
从图中可以看出来,IPL处理时可以分为三步:
- IPL接收控制;
- IPL将image加载到RAM中;
- IPL将控制权交给加载的image;
1.2 linearly mapped
linearly mapped设备的启动方式与bank-switched设备是一致的,不同点在于,不需要将整个image都拷贝到RAM中,如下图所示:
2. 自定义IPL程序
编写IPL程序,需要以下几个步骤:
- 初始化硬件,包括对系统RAM的访问。注意,只需要初始化必须的硬件(比如时钟等),外围硬件不需要初始化;(汇编实现)
- 将image镜像(使用mkifs生成)加载到RAM中,加载程序使用header信息来拷贝header和startup到RAM中,如果不是在linearly mapped的设备中,则需要将整个镜像拷贝到RAM中;(比如image_download_8250())
- 定位OS镜像,并做一些校验工作;(调用image_sacn())
- 拷贝startup程序;(调用image_setup())
- 跳转到加载镜像起始位置执行;(调用image_start())
3. IPL库
IPL库包含了一系列的接口,用于实现自定义的IPL程序,可用的函数接口如下:
4. Freescale IMX6 IPL
Freescale IMX6 BSP包:BSP_freescale-imx6SoloX-sabre-sdb_br-660_be-660_SVN815609_JBN555.zip
下载Freescale IMX6 BSP zip包并解压,IPL代码位于src/hardware/ipl/boards/mx6sx-sabre-sdb中,其中mx6sx-sabre-sdb.lnk为链接文件,指定了程序的入口以及内存的分段及布局等。
程序的入口:ENTRY(_start),在start.S文件中完成了以下工作:
- 设置CPU为SVC32模式
- Invalidate L1 I/D and TLBs
- Disable MMU和Caches
- 使能ICache
- 设置堆栈
- 跳转到main函数
在该目录中的main.c完成IPL的主要工作:
int main(void)
{
unsigned int image = QNX_LOAD_ADDR;
init_aips();
init_clocks();
init_pinmux();
init_sermx6(MXC_CONSOLE_BASE, 115200, 80000000, 2);
ser_putstr("\nWelcome to QNX Neutrino Initial Program Loader for:\n");
ser_putstr(" Freescale i.MX6 SoloX Sabre SDB (ARM Cortex-A9/M4)\n");
while (1) {
ser_putstr("Command:\n");
ser_putstr("Press 'D' for UART IFS download, using the 'sendnto' utility.\n");
ser_putstr("Press 'M' for SDMMC IFS download.\n");
ser_putstr("Press 'J' for JTAG IFS boot of image loaded to 0x"); ser_puthex(QNX_LOAD_ADDR); ser_putstr(".\n");
switch (ser_getchar()) {
case 'D': case 'd':
ser_putstr("send image now...\n");
if (image_download_ser(QNX_LOAD_ADDR)) {
ser_putstr(str_download_failed);
continue;
} else {
ser_putstr(str_download_ok);
}
break;
case 'M': case 'm':
if (sdmmc_load_file(QNX_LOAD_ADDR, QNX_IFS_FILENAME) != 0) {
ser_putstr(str_download_failed);
continue;
}
ser_putstr(str_download_ok);
break;
case 'J': case 'j':
break;
default:
break;
}
/* No safe boot media, must be scanned */
image = image_scan_2(image, image + MAX_SCAN, 1);
if (image != 0xffffffff) {
ser_putstr(str_found_image);
ser_puthex(image);
ser_putstr("\n");
image_setup_2(image);
ser_putstr(str_jump_to_image);
ser_puthex(startup_hdr.startup_vaddr);
ser_putstr("\n\n");
image_start_2(image);
/* Never reaches here */
return 0;
}
ser_putstr(str_image_scan_fail);
} /* Forever */
/* Never reaches here */
return 0;
}
进入main分别完成了以下工作:
- init_aips(),该函数用于设置AHB到IP Bridge的属性,跟Trust Zone相关;
- init_clocks(),该函数用于设置系统的时钟;
- init_pinmux(),该函数用于设置管脚的复用,主要是设置Uart和SD相关,其中Uart用于调试,而SD用于加载image;
- init_sermx6(),该函数用于初始化串口信息;
- image_download_ser()/sdmmc_load_file(),这两个函数用于完成Image的加载;
- image_scan_2(),该函数用于扫描image,对Image进行一些校验检查;
- image_start_2(),该函数跳转到Image的入口去执行,也就是跳转到startup程序中去运行;
从以上的流程可以看出IPL整体的功能并不复杂,完成最少硬件(需要用到的,比如时钟、串口、SD)的初始化,然后对Image加载和校验,最终跳转过去执行即可。
当然,我对这个IPL可运行性是持怀疑态度的,因为很重要的DDR Controler的相关初始化并没有看到。