|
Porting uClinux to Samsung S3C44B0X Board
一.Bootloader 理 論上,uClinux引導(dǎo)時(shí)并非一定需要一個(gè)獨(dú)立于Kernel Image的Bootloader Image。然而,將Bootloader與Kernel分開(kāi)設(shè)計(jì)能夠使軟件架構(gòu)更加清晰,也有助于靈活地支持多種引導(dǎo)方式,實(shí)現(xiàn)一些有用的輔助功能。 Bootloader的主要任務(wù)可以概括如下: 1.硬件初始化和系統(tǒng)引導(dǎo); 2.加載uClinux Kernel Image (如果需要); 3.設(shè)置需要傳遞給Kernel的啟動(dòng)參數(shù)(如果需要); 4.調(diào)用uClinux Kernel; 5.輔助功能:從主機(jī)下載新的Image; 6.輔助功能:燒寫(xiě)Flash Memory; 7.輔助功能:支持功能5和6所需的人機(jī)界面,如串行終端上的命令行接口。 對(duì) 于常見(jiàn)的幾類(lèi)處理器內(nèi)核,現(xiàn)在一般都找得到現(xiàn)成的Bootloader可用,不過(guò)需要針對(duì)具體的Board做些移植。在實(shí)現(xiàn)上述功能的前提下,也可以選擇 自行開(kāi)發(fā)。由于Bootloader Image在物理上獨(dú)立于Kernel Image,因此不一定選擇GNU作為開(kāi)發(fā)工具。對(duì)于以ARM7TDMI為內(nèi)核的S3C44B0X處理器,完全可以使用ADS來(lái)開(kāi)發(fā) Bootloader。 1.硬件初始化和系統(tǒng)引導(dǎo) 完整的Bootloader引導(dǎo)流程可描述如下: 硬 件初始化階段一 -> 復(fù)制二級(jí)Exception Vector Table -> 初始化各種處理器模式 -> 復(fù)制RO和RW,清零ZI -> (跳轉(zhuǎn)到C代碼入口函數(shù)) -> 初始化Exception/Interrupt Handler Entry Table -> 初始化Device Drivers -> 硬件初始化階段二 -> 建立人機(jī)界面 下面對(duì)上述各步驟逐一加以說(shuō)明。 1.1 硬件初始化階段一 板 子上電或復(fù)位后,程序從位于地址0x0的Reset Exception Vector處開(kāi)始執(zhí)行,因此需要在這里放置Bootloader的第一條指令:b ResetHandler,跳轉(zhuǎn)到標(biāo)號(hào)為ResetHandler處進(jìn)行第一階段的硬件初始化,主要內(nèi)容為:關(guān)Watchdog Timer,關(guān)中斷,初始化PLL和時(shí)鐘,初始化Memory Controller。比較重要的是PLL的輸出頻率要算正確,這里把它設(shè)置為50MHz;后面在計(jì)算SDRAM的Refresh Count和UART的Baud Rate等參數(shù)時(shí)還要用到。 1.2 復(fù)制二級(jí)Exception Vector Table Exception Vector Table是Bootloader與uClinux Kernel發(fā)生聯(lián)系的地方之一(另兩處是加載and/or調(diào)用Kernel,以及向Kernel傳遞啟動(dòng)參數(shù))。ARM7規(guī)定Exception Vector Table的基地址是0x0,所以Flash Memory的基地址也必須是0x0;而S3C44B0X處理器又不支持Memory Remap,這意味著無(wú)論運(yùn)行什么樣的上層軟件,一旦發(fā)生中斷,程序就得到Flash Memory中的Exception Vector Table里去打個(gè)轉(zhuǎn)(中斷Interrupt是異常Exception的一種)。對(duì)于uClinux而言,它會(huì)在RAM中建立自己的二級(jí) Exception Vector Table(后面將提到基地址被設(shè)為0x0C000000),所以在編寫(xiě)B(tài)ootloader時(shí),地址0x0處的一級(jí)Exception Vector Table只需簡(jiǎn)單地包含向二級(jí)Exception Vector Table跳轉(zhuǎn)的內(nèi)容: b ResetHandler ;Reset Handler ldr pc,=0x0c000004 ;Undefined Instruction Handler ldr pc,=0x0c000008 ;Software Interrupt Handler ldr pc,=0x0c00000c ;Prefetch Abort Handler ldr pc,=0x0c000010 ;Data Abort Handler b . ldr pc,=0x0c000018 ;IRQ Handler ldr pc,=0x0c00001c ;FIQ Handler LTORG 如 果在Bootloader執(zhí)行的全過(guò)程中都不必響應(yīng)中斷,那么上面的設(shè)置已能滿足要求。但如果某些Bootloader功能要求使用中斷(例如用 Timer Interrupt實(shí)現(xiàn)精確定時(shí),或利用External Interrupt支持Ethernet Driver以實(shí)現(xiàn)TFTP下載),那么Bootloader必須在同樣的地址處配置自己的二級(jí)Exception Vector Table,以便同uClinux兼容。這張表事先存放在Flash Memory里,引導(dǎo)過(guò)程中由Bootloader將其復(fù)制到RAM地址0x0C000000: 存放: RelocatedExceptionVectorStart mov pc,#0 b HandlerUndef b HandlerSWI b HandlerPAbort b HandlerDAbort b . b HandlerIRQ b HandlerFIQ HandlerUndef HANDLER HandleUndef HandlerSWI HANDLER HandleSWI HandlerPAbort HANDLER HandlePAbort HandlerDAbort HANDLER HandleDAbort HandlerIRQ HANDLER HandleIRQ HandlerFIQ HANDLER HandleFIQ LTORG RelocatedExceptionVectorEnd 復(fù)制: adr r0, RelocatedExceptionVectorStart ldr r2, =0x0c000000 adr r3, RelocatedExceptionVectorEnd 0 cmp r0, r3 ldrcc r1, [r0], #4 strcc r1, [r2], #4 bcc %B0 其中HANDLER是一個(gè)宏,用于查找Exception Handler Routines的入口地址。這些地址存放在由HandleXXX指向的表項(xiàng)中,該表定位在RAM高端,基地址為_(kāi)ISR_STARTADDRESS: ^ _ISR_STARTADDRESS HandleReset # 4 HandleUndef # 4 HandleSWI # 4 HandlePAbort # 4 HandleDAbort # 4 HandleReserved # 4 HandleIRQ # 4 HandleFIQ # 4 該表的內(nèi)容將在步驟1.5:“初始化Exception/Interrupt Handler Entry Table”中被填寫(xiě)為各Exception Handler Routine的入口地址。 1.3 初始化各種處理器模式 ARM7TDMI 支持7種Operation Mode:User,F(xiàn)IQ,IRQ,Supervisor,Abort,System和Undefined。Bootloader需要依次切換到每種模 式,初始化其程序狀態(tài)寄存器(SPSR)和堆棧指針(SP)。S3C44B0X處理器在上電或復(fù)位后處于Supervisor模式,本步驟中把對(duì) Supervisor模式的初始化放在最后,也就是說(shuō)Bootloader的后續(xù)部份仍將運(yùn)行在Supervisor模式下。 1.4 復(fù)制RO和RW,清零ZI 對(duì) 于ADS開(kāi)發(fā)工具而言,一個(gè)ARM程序由RO,RW和ZI三個(gè)段組成,其中RO為代碼段,RW是已初始化的全局變量,ZI是未初始化的全局變量(對(duì)于 GNU工具,對(duì)應(yīng)的概念是TEXT,DATA和BSS)。RO段既可以在Flash Memory中運(yùn)行,也可以在RAM中運(yùn)行。考慮到Bootloader中可能需要燒寫(xiě)Flash Memory,因此在引導(dǎo)階段應(yīng)當(dāng)將RO和RW段復(fù)制到RAM中,并將ZI段清零。當(dāng)RO段復(fù)制完畢之后,程序就可以跳轉(zhuǎn)到RAM中運(yùn)行。若不考慮燒寫(xiě) Flash Memory,則可以不復(fù)制RO段,程序始終在Flash Memory中運(yùn)行。ADS使用下列Linker Symbols來(lái)記錄各段的起始和結(jié)束地址: |Image$$RO$$Base| :RO段起始地址 |Image$$RO$$Limit| :RO段結(jié)束地址加1 |Image$$RW$$Base| :RW段起始地址 |Image$$RW$$Limit| :ZI段結(jié)束地址加1 |Image$$ZI$$Base| :ZI段起始地址 |Image$$ZI$$Limit| :ZI段結(jié)束地址加1 可 以在程序中引用這些標(biāo)號(hào)。需要注意的是,這些標(biāo)號(hào)的值是根據(jù)ARM Linker中RO Base和RW Base的設(shè)置來(lái)計(jì)算的,屬于“Linker Address”或“Execution Address”,并不一定代表這些段存放在Flash Memory中的地址,在編寫(xiě)復(fù)制程序時(shí)需要根據(jù)具體情況作相應(yīng)的計(jì)算。 1.5 初始化Exception/Interrupt Handler Entry Table 在 步驟1.2里已經(jīng)提到,需要在這一步中填寫(xiě)各Exception Handler Routine的入口地址。由于IRQ Exception為全部的中斷所共用,因此必須在IRQ Exception Handler Routine中根據(jù)中斷狀態(tài)寄存器判斷中斷源,并調(diào)用相應(yīng)的Interrupt Handler Routine。各Interrupt Handler Routine的入口地址也存放在上述的Exception/Interrupt Handler Entry Table中(緊接在HandleFIQ之后),需要在這一步中填寫(xiě),這里就不一一列出了。 另外,S3C44B0X處理器的 Interrupt Controller支持兩種中斷處理模式:Vectored Mode和Non-Vectored Mode,其中前者可能是Samsung特有的模式,并不被其它ARM7內(nèi)核所支持。考慮到代碼的可移植性,以上討論僅針對(duì)這里所使用的Non- Vectored Mode。 1.6 初始化Device Drivers 在這一步中需要為Bootloader用到的一些關(guān)鍵Device Drivers建立必要的數(shù)據(jù)結(jié)構(gòu),主要包括用于精確定時(shí)的Watchdog Timer Driver和用于支持串行終端的UART Driver。 1.7 硬件初始化階段二 繼 續(xù)對(duì)硬件進(jìn)行初始化,主要包括:GPIO,Cache,Interrupt Controller,Watchdog Timer和UARTs。S3C44B0X處理器內(nèi)置data/instruction合一的8KB Cache,且允許按地址范圍設(shè)置兩個(gè)Non-Cacheable區(qū)間。合理的配置是打開(kāi)對(duì)RAM區(qū)間的Cache,關(guān)閉對(duì)其它地址區(qū)間(包含F(xiàn)lash Memory區(qū)間)的Cache。所有硬件初始化完畢之后,開(kāi)中斷。 在步驟1.6和1.7中,仍然遵循“必要”的原則對(duì)硬件和Device Drivers進(jìn)行初始化。在目前階段沒(méi)有涉及的設(shè)備(如Ethernet Controller),可以留待使用它們之前再進(jìn)行初始化。 1.8 建立人機(jī)界面 引 導(dǎo)過(guò)程的最后一步是在串行終端上建立人機(jī)界面,并等待用戶(hù)輸入命令。合理的做法是先等待固定的秒數(shù),若在此期間未接收到用戶(hù)輸入,則直接從Flash Memory中加載and/or調(diào)用uClinux Kernel。若接收到用戶(hù)輸入,則顯示菜單模式或命令行模式的交互界面,等待用戶(hù)進(jìn)一步的命令。這里就不對(duì)此詳細(xì)討論了。 2.加載Kernel Bootloader是否需要執(zhí)行加載操作,取決于uClinux Kernel Image的類(lèi)型。根據(jù)不同的配置,可以生成下面幾種uClinux Kernel Image: 2.1 非壓縮,非XIP XIP(eXecute In Place)是指不對(duì)代碼段重新定位,在存放代碼段的位置就地運(yùn)行程序。該類(lèi)型的uClinux Kernel Image以非壓縮格式存放在Flash Memory中,由Bootloader加載到RAM中并直接調(diào)用。 2.2 非壓縮,XIP 該類(lèi)型的uClinux Kernel Image以非壓縮格式存放在Flash Memory中,不需加載,由Bootloader直接調(diào)用。復(fù)制init段和data段,清零bss段的工作由Kernel自行完成。 2.3 RAM自解壓 壓 縮格式的uClinux Kernel Image都是由開(kāi)頭的一段自解壓代碼和后面的壓縮數(shù)據(jù)部分組成。對(duì)于Kernel而言,由于是以壓縮格式存放,因次只能以非XIP方式執(zhí)行。RAM自解 壓類(lèi)型的uClinux Kernel Image存放在Flash Memory中,由Bootloader加載到RAM中的一段臨時(shí)空間,然后調(diào)用其自解壓代碼??蓤?zhí)行的uClinux Kernel被解壓到最終的執(zhí)行空間,然后運(yùn)行。壓縮格式Image所占據(jù)的臨時(shí)RAM空間可在隨后由uClinux回收利用。 2.4 ROM自解壓 解 壓縮操作也可以在Flash Memory中完成。實(shí)際上,這意味著以XIP方式執(zhí)行自解壓代碼。ROM自解壓類(lèi)型的uClinux Kernel Image存放在Flash Memory中,不需加載,由Bootloader直接調(diào)用其自解壓代碼。自解壓代碼自行復(fù)制其data段,清零bss段,然后將可執(zhí)行的uClinux Kernel解壓到最終的執(zhí)行空間并運(yùn)行之。與RAM自解壓相比,用ROM自解壓方式引導(dǎo)uClinux并不真正節(jié)省RAM,而且在Flash Memory中解壓縮速度較慢,因此實(shí)用價(jià)值不大。 2.5 Memory Map 下面給出Bootloader和 uClinux在存儲(chǔ)和運(yùn)行時(shí)的Memory Map。系統(tǒng)存儲(chǔ)器由NOR Flash Memory (2MB)和SDRAM(32MB)組成,F(xiàn)lash Memory的地址范圍從0x0到0x00200000,SDRAM的地址范圍從0x0C000000到0x0E000000。 Flash Memory 0x00000000 ~ 0x00020000: 存放Bootloader 0x00020000 ~ 0x00200000: 存放所有類(lèi)型的uClinux Kernel Image 運(yùn)行2.2類(lèi)型的uClinux Kernel 運(yùn)行2.4類(lèi)型的自解壓代碼 SDRAM 0x0C008000 ~ xxxxxxxx: 運(yùn)行Bootloader 0x0C200000 ~ xxxxxxxx: 運(yùn)行2.1,2.3,2.4類(lèi)型uClinux Kernel 0x0C400000 ~ xxxxxxxx: 臨時(shí)存放2.3類(lèi)型壓縮Image,并運(yùn)行自解壓 3.設(shè)置內(nèi)核啟動(dòng)參數(shù) Linux 2.4.x以后的內(nèi)核都期望以標(biāo)記列表(tagged list)的形式來(lái)傳遞啟動(dòng)參數(shù)。每個(gè)標(biāo)記存放在一個(gè)tag結(jié)構(gòu)中,每個(gè)tag結(jié)構(gòu)由標(biāo)識(shí)被傳遞參數(shù)的tag_header結(jié)構(gòu)以及隨后存放的參數(shù)值組 成。數(shù)據(jù)結(jié)構(gòu)tag、tag_header以及各種參數(shù)的數(shù)據(jù)結(jié)構(gòu)都定義在Linux內(nèi)核源碼的頭文件linux/include/asm- armnommu/setup.h中。 通常需要由Bootloader設(shè)置的啟動(dòng)參數(shù)有:ATAG_MEM、 ATAG_CMDLINE、ATAG_RAMDISK、ATAG_SERIAL、ATAG_INITRD等。啟動(dòng)參數(shù)的標(biāo)記列表以ATAG_CORE開(kāi) 始,以ATAG_NONE結(jié)束,代碼示例如下。其中0x0C000100是內(nèi)核啟動(dòng)參數(shù)在RAM中的基地址,指針params的類(lèi)型是struct tag。宏tag_next()以指向當(dāng)前標(biāo)記的指針為參數(shù),計(jì)算下一個(gè)標(biāo)記的起始地址。 params = (struct tag *)0x0C000100; 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); …… params->hdr.tag = ATAG_NONE; params->hdr.size = 0; 在Linux 內(nèi)核源碼的linux/arch/armnommu/mach-s3c44b0/arch.c中設(shè)置內(nèi)核啟動(dòng)參數(shù)在RAM中的基地址。如果Kernel不 需要從Bootloader接收啟動(dòng)參數(shù),下面代碼中的“BOOT_PARAMS(0x0C000100)”這一行可以不寫(xiě)。 MACHINE_START(S3C44B0, "44B0EVAL") MAINTAINER("XXX YYY") BOOT_MEM(DRAM_BASE,0x00000000,0x00000000) BOOT_PARAMS(0x0C000100) INITIRQ(genarch_init_irq) MACHINE_END uClinux Kernel處理啟動(dòng)參數(shù)時(shí)的代碼調(diào)用關(guān)系可查閱linux/init/main.c和 linux/arch/armnommu/kernel/setup.c:start_kernel()àsetup_arch() àparse_tags()。parse_tags()函數(shù)中調(diào)用parse_tag()函數(shù)依次處理每個(gè)標(biāo)記。parse_tag()函數(shù)先判斷 tag_header結(jié)構(gòu)中的標(biāo)記類(lèi)型,然后調(diào)用相應(yīng)的處理函數(shù)。例如,調(diào)用parse_tag_cmdline()處理ATAG_CMDLINE標(biāo)記, 調(diào)用parse_tag_initrd()處理ATAG_INITRD標(biāo)記,等等。對(duì)應(yīng)關(guān)系如下: static const struct tagtable core_tagtable[] __init = { { ATAG_CORE, parse_tag_core}, { ATAG_MEM, parse_tag_mem32}, { ATAG_VIDEOTEXT, parse_tag_videotext}, { ATAG_RAMDISK, parse_tag_ramdisk}, { ATAG_INITRD, parse_tag_initrd}, { ATAG_SERIAL, parse_tag_serialnr}, { ATAG_REVISION, parse_tag_revision}, { ATAG_CMDLINE, parse_tag_cmdline} }; 對(duì) 于Kernel Command Line,parse_tag_cmdline()函數(shù)將用內(nèi)核參數(shù)表中的命令字符串來(lái)覆蓋default_command_line[]變量。如果 Kernel不從Bootloader接收啟動(dòng)參數(shù),也可以有兩種方法來(lái)初始化Kernel Command Line。在linux/arch/armnommu/kernel/setup.c中有: static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE; 因 此一種方法是在make menuconfig時(shí)通過(guò)修改“General Setup”子菜單中的“Default kernel command string”選項(xiàng)來(lái)定義linux/include/linux/autoconf.h頭文件中的CONFIG_CMDLINE宏,另一種方法是在 linux/arch/armnommu/kernel/setup.c中直接把default_command_line[]寫(xiě)死。 4.調(diào)用Kernel Bootloader調(diào)用uClinux Kernel的方法是直接跳轉(zhuǎn)到Kernel的第一條指令處。在跳轉(zhuǎn)時(shí)要滿足下列條件: CPU寄存器r0=0; CPU 寄存器r1=Machine Type ID(S3C44B0X的Machine Type ID定義在include/asm-arm/mach-types.h中:#define MACH_TYPE_S3C44B0 178。寄存器r1也可以在Kernel啟動(dòng)之初的head-armv.S中設(shè)置); 禁止中斷(IRQs和FIQs); CPU運(yùn)行在SVC模式; MMU必須關(guān)閉(S3C44B0X沒(méi)有MMU); 指令Cache可以打開(kāi)也可以關(guān)閉,數(shù)據(jù)Cache必須關(guān)閉(S3C44B0X的Cache是指令與數(shù)據(jù)合一的,因此只能選擇關(guān)閉)。 C代碼調(diào)用Kernel的示例如下,其中r0和r1的值通過(guò)參數(shù)傳遞: void (*CallKernel)(int zero, int mach) = (void (*)(int, int))KERNEL_ADDR; CallKernel(0, 178); 5.輔助功能 完 整的Bootloader還應(yīng)該支持從主機(jī)下載文件到目標(biāo)板的RAM;用RAM中的數(shù)據(jù)燒寫(xiě)Flash Memory;以及上述功能所需的人機(jī)交互接口。文件下載途徑視目標(biāo)板所提供的物理通訊接口而定,比較簡(jiǎn)單的方法一般是通過(guò)串口,用Xmodem或 Ymodem協(xié)議下載,但速度較慢。目標(biāo)板上只需要實(shí)現(xiàn)協(xié)議的接收部份,主機(jī)上可以用HyperTerminal等工具來(lái)發(fā)送文件。如果目標(biāo)板提供 Ethernet等快速接口,也可以移植一個(gè)簡(jiǎn)單的TCP/IP棧,用TFTP等標(biāo)準(zhǔn)文件傳輸協(xié)議下載。目前Flash Memory一般都是用NOR Flash,燒寫(xiě)是非常簡(jiǎn)單的;需要注意的是對(duì)于多數(shù)Flash芯片,在Erase/Program之前需要先Unprotect。人機(jī)交互接口不難在串 行終端上實(shí)現(xiàn),這里就不贅述了。 參考文獻(xiàn): 1. S3C44B0X User‘s Manual Samsung 2.嵌入式系統(tǒng)Boot Loader技術(shù)內(nèi)幕 詹榮開(kāi) |
|
|