本文主要描述Initial program loader的相關內容,並以Texas Instruments DRA74x Jacinto6 Cortex A15處理器爲例講解
Initial Program Loaders (IPLs)
IPL 初始化硬件,爲 C 語言環境設置堆棧,將操作系統映像加載到內存中,最後跳轉到 QNX 的startup
代碼。
IPL是非常簡化的,功能可以類比Uboot;它使用盡可能少的代碼來運行啓動代碼。 IPL 首先設置執行其編譯的 C 代碼所需的最小環境,然後查找 OS 映像並將其加載到內存中。因此,IPL 按順序執行以下任務:
- 完成硬件初始化,對硬件進行最低限度的配置。
- 爲 C 語言環境設置堆棧。
- 查找並驗證OS映像。
- 將操作系統映像加載到內存中。
- 跳轉到操作系統
startup
代碼。
IPL 可以存儲在linearly-mapped device
(例如 NOR 閃存設備)或non-linearly-mapped device
(例如 eMMC、SD 卡或 SPI NOR 閃存設備)上。 與電路板架構一樣,存儲 IPL 和 OS 映像的存儲設備類型決定了 IPL 的設計。
影響IPL設計因素
IPL 的設計和內容受多種因素影響,包括:
Board 板級因素
IPL 是特定於硬件架構的。 x86 處理器和 ARM 處理器的 IPL 差異很大。(請參閱“From reset to startup從復位到啓動代碼)”)。
除了特定於架構之外,IPL 還特定於它運行的電路板(甚至跟電路板種類有關)。 它必須進行定製,以便知道如何配置內存控制器(例如DDR)、設置時鐘和執行其他硬件初始化。
Supported removeable media 支持的可移動存儲設備
IPL 必須考慮電路板從中檢索引導映像的可移動介質的類型,因爲該可移動介質決定了 IPL 可以使用的引導策略和技術。 即支持linear media,又non-linear media(例如,USB 密鑰或板載 eMMC、NOR 閃存或 NAN 閃存)已成爲最常見的選擇。
Size of the storage media 存儲設備的大小
IFS 映像是壓縮還是未壓縮取決於存儲介質的大小和性能。 對於壓縮和未壓縮圖像,IPL 將 IFS 複製到 RAM,然後跳轉到啓動代碼。 啓動代碼確定圖像是否被壓縮,解壓縮它(如果它被壓縮),然後將它重向到 RAM 中的適當位置。 因爲所有這些任務都會增加引導過程的時間,所以您可以配置引導過程以將 IFS 拷貝到其最終位置,而不是啓動代碼。
From reset to startup 從復位到啓動代碼
由於單板架構和處理器的差異導致單板的估計處理也是不一樣的。x86 和 ARM 平臺的 IPL 差異很大。
儘管存在差異,但所有系列的單板的 IPL 完成將 IFS 映像加載到內存並跳轉到 OS 啓動代碼工作是一樣的。 他們至少有以下共同點:
- 它們是在單板上首先執行的軟件代碼(單板固件 BIOS 或 Boot ROM)。
- 最終控制權移交給操作系統內核啓動代碼(請參閱“Startup Programs“)。
下圖顯示了初始化和單板啓動的各個組件的順序。ARM是從Boot ROM裏啓動,X86是從BIOS裏啓動。
Image storage鏡像存儲
存儲可引導映像的介質以及它的存儲方式(壓縮或未壓縮)決定了 IPL 必須對映像做什麼。
用於存儲 IPL 和 IFS 的可移動介質類型影響 IPL 設計的許多方面。 最重要的因素之一是存儲介質是線性映射(linearly-mapped)(例如,NOR FLASH)還是非線性映射(non-linearly-mapped)(例如,eMMC、SD 卡),因爲它決定了 IPL 是否能使用 XIP。
其他重要因素包括圖像的存儲方式(壓縮時不壓縮),以及存儲它的設備(eMMC、SD 卡、硬盤等;參見“Image storage methods”)。
Linearly-mapped devices 線性映射設備
對於線性映射設備(例如,ROM 設備),整個映像位於可直接尋址的存儲中,該存儲將其整個地址空間映射到處理器的地址空間。 處理器可以尋址 OS 映像中的任何位置,因此 IPL 只需將啓動代碼(而不是整個映像)複製到 RAM 中。
由於線性映射的設備可以直接映射到處理器的地址空間,IPL 只能將啓動代碼直接從該設備加載到 RAM 中,其餘的映像留在設備上。
如果可移動存儲設備是線性映射,IPL能夠使用就地執行XIP(eXecute In Place)。如果使用 XIP,則鏈接器文件(例如 mx6sx-sabre-sdb.lnk 或 vayu-evm.lnk)中 IPL(代碼)的 .text 部分必須有線性映射地址空間。這意味着所有的IPL的彙編代碼在鏡像拷貝到RAM之前都可以被執行。
Non-linearly-mapped devices 非線性映射設備
對於非線性映射設備(例如 eMMC、SD 卡或 SPI NOR 設備),存儲鏡像位於無法直接映射到處理器地址空間中。 處理器無法尋址 OS 映像,因此 IPL 需要將整個映像(包括啓動代碼)複製到 RAM 中。
QNX IPL 從非線性映射設備加載整個圖像,因爲這些設備上的處理器地址不能直接映射到處理器的地址空間。
Compressed images 壓縮鏡像
如果可啓動映像太大而無法以其原始格式存儲在可移動存儲媒體上,或者如果硬件限制(例如,慢速總線)使複製和解壓映像比以原始格式複製映像更有效,則可啓動映像可能是壓縮在線性映射或非線性映射的可移動存儲設備上。
在構建 OS 映像時,mkifs
工具會檢查構建文件中的 +compress
屬性。 如果設置了屬性(例如,[virtual = x86_64, bios +compress boot = {),mkifs 將壓縮映像(請參閱 "OS Image Buildfiles" 章節)。
如果鏡像被壓縮,則必須在 IPL 的第二階段中的 main() 函數調用 image_scan() 函數之前將其複製到 RAM 並解壓。 當您編寫 IPL 或更新使用壓縮映像的系統時,請注意以下事項:
將壓縮鏡像複製到 RAM 中的位置,而不是您將解壓它的地址。
解壓鏡像到啓動代碼跳轉地址,以便位於您的 IPL 完成跳轉。
在複製壓縮鏡像的位置和解壓圖像的地址之間爲整個未壓縮鏡像留出足夠的空間。
如果您更改或添加組件,這可能會增加鏡像的大小。 檢查在未壓縮圖像的起始地址和您第一次將壓縮圖像複製到 RAM 的地址之間是否有足夠的空間容納新的、更大的鏡像。
Image locations 鏡像位置
IPL 從不同類型的設備或通過網絡加載操作系統映像,具體取決於主板支持什麼方式。 存儲可啓動映像的更常見位置包括:
Disk 磁盤
大多數嵌入式系統現在使用固態媒體,例如 U盤、SD和micro SD 卡或 eMMC,還有可能仍使用旋轉磁盤。
對於x86的系統來說,從磁盤引導是最簡單的引導方法。BIOS 或 UEFI 執行所有工作:它從磁盤獲取映像,將其傳輸到 RAM,然後啓動它。
對於ARM的系統來說,從磁盤啓動至少需要:
需要有驅動程序知道如何訪問磁盤
需要有檢查磁盤分區表並定位操作系統映像的IPL代碼
需要有將鏡像從磁盤硬件(旋轉磁盤)獲取映射並按字節方式傳輸到RAM(固態磁盤)某個地址空間。
Network
如果板上有一個 PCI 網卡,操作系統映像可以通過以太網加載,並放置在處理器的地址空間中。
BOOTP server
在某些嵌入式板上,ROM 監視器包含 BOOTP 代碼。 此代碼知道如何與網絡硬件通信,以及如何從遠程系統獲取操作系統映像。
要使用 BOOTP 啓動 QNX Neutrino 系統,您需要一個用於 OS 客戶端的 BOOTP ROM 和一個 BOOTP 服務器(例如 bootpd)。 由於使用TFTP協議將鏡像從服務器移動到客戶端,因此還需要一個TFTP服務器; 在大多數系統上,此服務器隨 BOOTP 服務器一起提供(請參閱Utilities Reference中的 bootpd)。
Serial port
目標上的串行端口在開發過程中可用於下載映像或作爲故障安全機制(例如,如果校驗和失敗,您可以簡單地通過串行端口重新加載操作系統映像)。
串行加載器可以內置到 IPL 代碼中,以便 IPL 可以從外部硬件端口獲取 OS 映像。 IPL 過程與用於網絡引導的過程幾乎相同,不同之處在於使用串行端口替代了以太網端口來獲取操作系統映像。
串行加載器通常對嵌入式系統的成本影響很小。 電路板供應商通常在他們的評估板上包含串行端口功能,有時通過另一個端口,例如調試端口。 在很多情況下,串口硬件和串口加載器可以在項目開發過程中使用,然後從最終產品中省去。
QNX 爲 8250 芯片的嵌入式串行加載器提供源代碼,QNX Neutrino SDP 包含一個Utility
,可通過串行或並行端口將操作系統映像發送到目標板(請參閱Utilities Reference中的“sendnto”)。
IPL庫
Function | Description |
---|---|
enable_cache() | Enable the cache |
image_download_8250() | Download an OS image |
image_download_ser() | Download an OS image |
image_scan(), image_scan_2(), image_scan_ext() | Scans memory for a valid system image |
image_setup(), image_setup_2(), image_setup_ext() | Copy the startup code and OS image into RAM and prepare it for execution |
image_start(), image_start_2(), image_start_ext() | Jump to the bootable OS image start point |
init_8250() | Initialize an 8250 serial port to 8N1 parameters |
int15_copy() | Copy data from high memory (above 1 MB) to a buffer, or to low memory (below 1 MB) |
jump() | Jump to the specifed in address in memory |
print_byte() | Output one byte to video (x86 only) |
print_char() | Display a character to video. |
print_long() | Display a long to video |
print_sl() | Displays to video a string, followed by a long (x86 only) |
print_string() | Display a string to video |
print_var() | Display a variable to video |
print_word() | Display a word to video |
protected_mode() | Switches the x86 processor to protected mode (non-BIOS systems) |
uart_hex8 | Output an 8-bit hex number to the UART (x86 only) |
uart_hex16 | Output a 16-bit hex number to the UART (x86 only) |
uart_hex32 | Output a 32-bit hex number to the UART (x86 only) |
uart_init | Initializes the on-chip UART to 8 data bits, 1 stop bit, and no parity (8250 compatible) |
uart_put | Output single character to UART (x86 only) |
uart_string | Output a NULL-terminated string to the UART (x86 only) |
uart32_hex8 | Output a 16-bit hex number to the UART (x86 only) |
uart32_hex16 | Output a 16-bit hex number to the UART (x86 only) |
uart32_hex32 | Outputs a 32-bit hex number to the UART (x86 only) |
uart32_init | Initializes the on-chip UART to 8 data bits, 1 stop bit, and no parity (8250 compatible) |
uart32_put | Output a single character to the UART (x86 only) |
uart32_string | Output a NULL-terminated string to the UART (x86 only) |
Texas Instruments DRA74x Jacinto6 ARM IPLs
TI DRA74x Jacinto6 BSP包:BSP_ti-j6-dra74x_vayu-evm_br-660_be-660_SVN817036_JBN156.zip
下載TI DRA74x Jacinto6 BSP zip包並解壓,IPL代碼位於src/hardware/ipl/boards/dra74x/中,其中src/hardware/ipl/boards/dra74x/arm/vayu-evm.le.v7.64/vayu-evm.lnk爲鏈接文件,指定了程序的入口以及內存的分段及佈局等。
TARGET(elf32-littlearm)
OUTPUT_FORMAT(elf32-littlearm)
ENTRY(_start)
MEMORY
{
ram : ORIGIN = 0x40300000, LENGTH = 0x14000
/* TLB table must be align to 16K (0x4000) */
tlb : ORIGIN = 0x40314000, LENGTH = 0x4000
/* dedicated for non-cacheble buffer */
scratch : ORIGIN = 0x80000000, LENGTH = 0x100000
}
SECTIONS
{
.text : {
_start.o(.text)
*(.text)
*(.rodata*)
*(.glue_7)
*(.glue_7t)
} > ram
.note.gnu.build-id : {
*(.note.gnu.build-id)
} > ram
_etext = .;
.data : {
*(.data)
*(.sdata)
} > ram
_edata = .;
/*
* The .bss.dummy section needs to be placed at the beginning of .bss
* and right after the .data,
* in order to protect the _TI_ROM_r0 from being overwritten
* when loading IPL from NOR
*/
.bss : {
*(.bss.dummy)
*(.bss)
*(.sbss)
} > ram
.stack : {
*.(*)
} > ram
.tlb : {
*(.tlb);
} > tlb
.scratch : {
*(.scratch);
} > scratch
}
程序的入口:ENTRY(_start),在 src/hardware/ipl/boards/dra74x/_start.S 文件中完成了以下工作:
- 設置CPU爲SVC32模式
- Invalidate L1 I/D and TLBs
- Disable MMU stuff 和caches
- 確定 IPL 是否從 SRAM 啓動
- 設置堆棧
- 跳轉到main函數 在src/hardware/ipl/boards/dra74x/arm/vayu-evm/目錄中的main.c完成IPL的主要工作:
int main(void)
{
unsigned image, start;
IPL_BootOpt_t bootOpt;
chip_revision = get_omap_rev();
#if defined (CACHE_ENABLED)
enable_cache_mmu();
#endif
/* Initialize Pin Muxing, SDRAM, Clocks etc. */
init_hw_platform();
/*
* The eMMC part takes 400-600ms between the first CMD1 and fully powered up
* Thus issuing CMD0 and CMD1 in IPL will significantly reduce the startup time of MMCSD driver on eMMC
*/
powerup_emmc(2);
log_putstr("\nQNX Neutrino Initial Program Loader for DRA74X Vayu EVM\n");
print_omap_revision();
bootOpt = ipl_boot_detect();
#if MANBOOT
bootOpt = ipl_boot_menu();
#endif
while (1) {
image = QNX_LOAD_ADDR;
switch (bootOpt) {
case IPL_BOOT_SD:
if (sdmmc_load_file(SD_INTERFACE, 1, image, "QNX-IFS", 0) != 0) {
ser_putstr("load image from SD failed\n");
goto print_boot_menu;
}
break;
case IPL_BOOT_EMMC:
if (sdmmc_load_file(EMMC_INTERFACE, 2, image, "QNX-IFS", 0) != 0) {
ser_putstr("load image from eMMC failed\n");
goto print_boot_menu;
}
break;
case IPL_BOOT_QSPI:
if (qspi_load_image(QSPI_IFS_OFFSET(0), image) != 0) {
ser_putstr("load image from QSPI flash failed\n");
goto print_boot_menu;
}
break;
case IPL_BOOT_SERIAL:
ser_putstr("Send IFS image through serial now...\n");
if (image_download_ser(image) != 0) {
ser_putstr("Download image failed\n");
goto print_boot_menu;
}
ser_putstr("Download ok...\n");
/* get remaining bytes */
while (ser_poll())
ser_getchar();
break;
case IPL_MEM_TEST:
sdram_test(DRA74x_EMIF_CS0_BASE, MEG(1536));
default:
goto print_boot_menu;
}
if (bootOpt == IPL_BOOT_SERIAL) {
start = image_scan_2(image, image + MAX_SCAN, 0);
} else {
start = image_scan_2(image, image + MAX_SCAN, 1);
}
if (start != 0xffffffff) {
#ifdef HS_AUTH_IFS
/* Authenticate loaded image before jumping execution */
authenticate_image_signature(image, startup_hdr.stored_size + start - image + IFS_CERT_SIZE);
#endif
log_putstr("Found image @ 0x");
log_puthex(start);
log_putstr("\n");
image_setup_2(start);
log_putstr("Jumping to startup @ 0x");
log_puthex(startup_hdr.startup_vaddr);
log_putstr("\n\n");
#if defined (CACHE_ENABLED)
/*
* Flush D-cache and invalidate I-cache before jumping
*/
arm_v7_dcache_flush();
arm_v7_icache_invalidate();
#endif
#if defined(DEBUG_BOOT_TIMING)
omap_timer_curr("IPL total", TIMING_MILLI_SECOND);
#endif
image_start_2(start);
/* Never reach here */
return 0;
}
ser_putstr("Image_scan failed...\n");
print_boot_menu:
bootOpt = ipl_boot_menu();
}
return 0;
}
進入main分別完成了以下工作:
- enable_cache_mmu() 該函數用於使能cache和mmu
- init_hw_platform(), 該函數用於設置管腳的複用,系統的時鐘,串口初始化,DDR初始化等。
- powerup_emmc(),該函數用於eMMC初始化。
- ipl_boot_detect(), 該函數用於啓動模式檢測,分別包含串口,SD卡,eMMC和QSPI NOR Flash加載啓動。
- sdmmc_load_file()/qspi_load_image()/image_download_ser 這三個函數用於完成Image的加載;
- image_scan_2(),該函數用於掃描image,對Image進行一些校驗檢查;
- image_start_2(),該函數跳轉到Image的入口去執行,也就是跳轉到startup程序中去運行;
從以上的流程可以看出IPL整體的功能並不複雜,完成最少硬件(需要用到的,比如時鐘、串口、SD)的初始化,然後對Image加載和校驗,最終跳轉過去執行即可。