字體:小 中 大 | |
|
|
2007/01/04 18:11:20瀏覽1604|回應1|推薦0 | |
轉帖]Porting uClinux to Samsung S3C44B0X Board by Ryan SHENG ryansheng@sohu.com from EDW 一.Bootloader 理論上,uClinux引導時並非一定需要一個獨立於Kernel Image的Bootloader Image。然而,將Bootloader與Kernel分開設計能夠使軟體架構更加清晰,也有助於靈活地支援多種引導方式,實現一些有用的輔助功能。 Bootloader的主要任務可以概括如下: 1.硬體初始化和系統引導; 2.載入uClinux Kernel Image (如果需要); 3.設置需要傳遞給Kernel的啟動參數(如果需要); 4.調用uClinux Kernel; 5.輔助功能:從主機下載新的Image; 6.輔助功能:燒寫Flash Memory; 7.輔助功能:支援功能5和6所需的人機界面,如串列終端上的命令行介面。 對於常見的幾類處理器內核,現在一般都找得到現成的Bootloader可用,不過需要針對具體的Board做些移植。在實現上述功能的前提下,也可以選 擇自行開發。由於Bootloader Image在物理上獨立於Kernel Image,因此不一定選擇GNU作為開發工具。對於以ARM7TDMI為內核的S3C44B0X處理器,完全可以使用ADS來開發 Bootloader。 1.硬體初始化和系統引導 完整的Bootloader引導流程可描述如下: 硬體初始化階段一 -> 複製二級Exception Vector Table -> 初始化各種處理器模式 -> 複製RO和RW,清零ZI -> (跳轉到C代碼入口函數) -> 初始化Exception/Interrupt Handler Entry Table -> 初始化Device Drivers -> 硬體初始化階段二 -> 建立人機界面 下面對上述各步驟逐一加以說明。 1.1 硬體初始化階段一 板子上電或重定後,程式從位於位址0x0的Reset Exception Vector處開始執行,因此需要在這裏放置Bootloader的第一條指令:b ResetHandler,跳轉到標號為ResetHandler處進行第一階段的硬體初始化,主要內容為:關Watchdog Timer,關中斷,初始化PLL和時鐘,初始化Memory Controller。比較重要的是PLL的輸出頻率要算正確,這裏把它設置為50MHz;後面在計算SDRAM的Refresh Count和UART的Baud Rate等參數時還要用到。 1.2 複製二級Exception Vector Table Exception Vector Table是Bootloader與uClinux Kernel發生聯繫的地方之一(另兩處是載入and/or調用Kernel,以及向Kernel傳遞啟動參數)。ARM7規定Exception Vector Table的基底位址是0x0,所以Flash Memory的基底位址也必須是0x0;而S3C44B0X處理器又不支援Memory Remap,這意味著無論運行什麼樣的上層軟體,一旦發生中斷,程式就得到Flash Memory中的Exception Vector Table裏去打個轉(中斷Interrupt是異常Exception的一種)。對於uClinux而言,它會在RAM中建立自己的二級 Exception Vector Table(後面將提到基底位址被設為0x0C000000),所以在編寫Bootloader時,位址0x0處的一級Exception Vector Table只需簡單地包含向二級Exception Vector Table跳轉的內容: 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執行的全過程中都不必回應中斷,那麼上面的設置已能滿足要求。但如果某些Bootloader功能要求使用中斷(例如用 Timer Interrupt實現精確定時,或利用External Interrupt支持Ethernet Driver以實現TFTP下載),那麼Bootloader必須在同樣的位址處配置自己的二級Exception Vector Table,以便同uClinux相容。這張表事先存放在Flash Memory裏,引導過程中由Bootloader將其複製到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 複製: adr r0, RelocatedExceptionVectorStart ldr r2, =0x0c000000 adr r3, RelocatedExceptionVectorEnd 0 cmp r0, r3 ldrcc r1, [r0], #4 strcc r1, [r2], #4 bcc %B0 其中HANDLER是一個宏,用於查找Exception Handler Routines的入口位址。這些位址存放在由HandleXXX指向的表項中,該表定位在RAM高端,基底位址為_ISR_STARTADDRESS: ^ _ISR_STARTADDRESS HandleReset # 4 HandleUndef # 4 HandleSWI # 4 HandlePAbort # 4 HandleDAbort # 4 HandleReserved # 4 HandleIRQ # 4 HandleFIQ # 4 該表的內容將在步驟1.5:“初始化Exception/Interrupt Handler Entry Table”中被填寫為各Exception Handler Routine的入口地址。 1.3 初始化各種處理器模式 ARM7TDMI支持7種Operation Mode:User,FIQ,IRQ,Supervisor,Abort,System和Undefined。Bootloader需要依次切換到每種模 式,初始化其程式狀態寄存器(SPSR)和堆疊指標(SP)。S3C44B0X處理器在上電或重定後處於Supervisor模式,本步驟中把對 Supervisor模式的初始化放在最後,也就是說Bootloader的後續部份仍將運行在Supervisor模式下。 1.4 複製RO和RW,清零ZI 對於ADS開發工具而言,一個ARM程式由RO,RW和ZI三個段組成,其中RO為代碼段,RW是已初始化的總體變數,ZI是未初始化的總體變數(對於 GNU工具,對應的概念是TEXT,DATA和BSS)。RO段既可以在Flash Memory中運行,也可以在RAM中運行。考慮到Bootloader中可能需要燒寫Flash Memory,因此在引導階段應當將RO和RW段複製到RAM中,並將ZI段清零。當RO段複製完畢之後,程式就可以跳轉到RAM中運行。若不考慮燒寫 Flash Memory,則可以不複製RO段,程式始終在Flash Memory中運行。ADS使用下列Linker Symbols來記錄各段的起始和結束位址: |Image$$RO$$Base| :RO段起始位址 |Image$$RO$$Limit| :RO段結束地址加1 |Image$$RW$$Base| :RW段起始位址 |Image$$RW$$Limit| :ZI段結束地址加1 |Image$$ZI$$Base| :ZI段起始位址 |Image$$ZI$$Limit| :ZI段結束地址加1 可以在程式中引用這些標號。需要注意的是,這些標號的值是根據ARM Linker中RO Base和RW Base的設置來計算的,屬於“Linker Address”或“Execution Address”,並不一定代表這些段存放在Flash Memory中的位址,在編寫複製程式時需要根據具體情況作相應的計算。 1.5 初始化Exception/Interrupt Handler Entry Table 在步驟1.2裏已經提到,需要在這一步中填寫各Exception Handler Routine的入口地址。由於IRQ Exception為全部的中斷所共用,因此必須在IRQ Exception Handler Routine中根據中斷狀態寄存器判斷中斷源,並調用相應的Interrupt Handler Routine。各Interrupt Handler Routine的入口位址也存放在上述的Exception/Interrupt Handler Entry Table中(緊接在HandleFIQ之後),需要在這一步中填寫,這裏就不一一列出了。 另外,S3C44B0X處理器的Interrupt Controller支援兩種中斷處理模式:Vectored Mode和Non-Vectored Mode,其中前者可能是Samsung特有的模式,並不被其他ARM7內核所支援。考慮到代碼的可攜性,以上討論僅針對這裏所使用的Non- Vectored Mode。 1.6 初始化Device Drivers 在這一步中需要為Bootloader用到的一些關鍵Device Drivers建立必要的資料結構,主要包括用於精確定時的Watchdog Timer Driver和用於支援串列終端的UART Driver。 1.7 硬體初始化階段二 繼續對硬體進行初始化,主要包括:GPIO,Cache,Interrupt Controller,Watchdog Timer和UARTs。S3C44B0X處理器內置data/instruction合一的8KB Cache,且允許按位址範圍設置兩個Non-Cacheable區間。合理的配置是打開對RAM區間的Cache,關閉對其他地址區間(包含Flash Memory區間)的Cache。所有硬體初始化完畢之後,開中斷。 在步驟1.6和1.7中,仍然遵循“必要”的原則對硬體和Device Drivers進行初始化。在目前階段沒有涉及的設備(如Ethernet Controller),可以留待使用它們之前再進行初始化。 1.8 建立人機界面 引導過程的最後一步是在串列終端上建立人機界面,並等待用戶輸入命令。合理的做法是先等待固定的秒數,若在此期間未接收到用戶輸入,則直接從Flash Memory中載入and/or調用uClinux Kernel。若接收到用戶輸入,則顯示功能表模式或命令行模式的交互介面,等待用戶進一步的命令。這裏就不對此詳細討論了。 2.載入Kernel Bootloader是否需要執行載入操作,取決於uClinux Kernel Image的類型。根據不同的配置,可以生成下面幾種uClinux Kernel Image: 2.1 非壓縮,非XIP XIP(eXecute In Place)是指不對代碼段重新定位,在存放代碼段的位置就地運行程式。該類型的uClinux Kernel Image以非壓縮格式存放在Flash Memory中,由Bootloader載入到RAM中並直接調用。 2.2 非壓縮,XIP 該類型的uClinux Kernel Image以非壓縮格式存放在Flash Memory中,不需載入,由Bootloader直接調用。複製init段和data段,清零bss段的工作由Kernel自行完成。 2.3 RAM自解壓 壓縮格式的uClinux Kernel Image都是由開頭的一段自解壓代碼和後面的壓縮資料部分組成。對於Kernel而言,由於是以壓縮格式存放,因次只能以非XIP方式執行。RAM自解 壓類型的uClinux Kernel Image存放在Flash Memory中,由Bootloader載入到RAM中的一段臨時空間,然後調用其自解壓代碼。可執行的uClinux Kernel被解壓到最終的執行空間,然後運行。壓縮格式Image所佔據的臨時RAM空間可在隨後由uClinux回收利用。 2.4 ROM自解壓 解壓縮操作也可以在Flash Memory中完成。實際上,這意味著以XIP方式執行自解壓代碼。ROM自解壓類型的uClinux Kernel Image存放在Flash Memory中,不需載入,由Bootloader直接調用其自解壓代碼。自解壓代碼自行複製其data段,清零bss段,然後將可執行的uClinux Kernel解壓到最終的執行空間並運行之。與RAM自解壓相比,用ROM自解壓方式引導uClinux並不真正節省RAM,而且在Flash Memory中解壓縮速度較慢,因此實用價值不大。 2.5 Memory Map 下面給出Bootloader和uClinux在存儲和運行時的Memory Map。系統記憶體由NOR Flash Memory (2MB)和SDRAM(32MB)組成,Flash Memory的位址範圍從0x0到0x00200000,SDRAM的地址範圍從0x0C000000到0x0E000000。 Flash Memory 0x00000000 ~ 0x00020000: 存放Bootloader 0x00020000 ~ 0x00200000: 存放所有類型的uClinux Kernel Image 運行2.2類型的uClinux Kernel 運行2.4類型的自解壓代碼 SDRAM 0x0C008000 ~ xxxxxxxx: 運行Bootloader 0x0C200000 ~ xxxxxxxx: 運行2.1,2.3,2.4類型uClinux Kernel 0x0C400000 ~ xxxxxxxx: 臨時存放2.3類型壓縮Image,並運行自解壓 3.設置內核啟動參數 Linux 2.4.x以後的內核都期望以標記列表(tagged list)的形式來傳遞啟動參數。每個標記存放在一個tag結構中,每個tag結構由標識被傳遞參數的tag_header結構以及隨後存放的參數值組 成。資料結構tag、tag_header以及各種參數的資料結構都定義在Linux內核源碼的頭檔linux/include/asm- armnommu/setup.h中。 通常需要由Bootloader設置的啟動參數有:ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_SERIAL、 ATAG_INITRD等。啟動參數的標記列表以ATAG_CORE開始,以ATAG_NONE結束,代碼示例如下。其中0x0C000100是內核啟動 參數在RAM中的基底位址,指標params的類型是struct tag。巨集tag_next()以指向當前標記的指標為參數,計算下一個標記的起始位址。 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內核源碼的linux/arch/armnommu/mach-s3c44b0/arch.c中設置內核啟動參數在RAM中的基底位址。如果 Kernel不需要從Bootloader接收啟動參數,下面代碼中的“BOOT_PARAMS(0x0C000100)”這一行可以不寫。 MACHINE_START(S3C44B0, "44B0EVAL") MAINTAINER("XXX YYY") BOOT_MEM(DRAM_BASE,0x00000000,0x00000000) BOOT_PARAMS(0x0C000100) INITIRQ(genarch_init_irq) MACHINE_END uClinux Kernel處理啟動參數時的代碼調用關係可查閱linux/init/main.c和 linux/arch/armnommu/kernel/setup.c:start_kernel()àsetup_arch() àparse_tags()。parse_tags()函數中調用parse_tag()函數依次處理每個標記。parse_tag()函數先判斷 tag_header結構中的標記類型,然後調用相應的處理函數。例如,調用parse_tag_cmdline()處理ATAG_CMDLINE標記, 調用parse_tag_initrd()處理ATAG_INITRD標記,等等。對應關係如下: 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} }; 對於Kernel Command Line,parse_tag_cmdline()函數將用內核參數表中的命令字串來覆蓋default_command_line[]變數。如果 Kernel不從Bootloader接收啟動參數,也可以有兩種方法來初始化Kernel Command Line。在linux/arch/armnommu/kernel/setup.c中有: static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE; 因此一種方法是在make menuconfig時通過修改“General Setup”子功能表中的“Default kernel command string”選項來定義linux/include/linux/autoconf.h頭檔中的CONFIG_CMDLINE宏,另一種方法是在 linux/arch/armnommu/kernel/setup.c中直接把default_command_line[]寫死。 4.調用Kernel Bootloader調用uClinux Kernel的方法是直接跳轉到Kernel的第一條指令處。在跳轉時要滿足下列條件: 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啟動之初的head-armv.S中設置); 禁止中斷(IRQs和FIQs); CPU運行在SVC模式; MMU必須關閉(S3C44B0X沒有MMU); 指令Cache可以打開也可以關閉,資料Cache必須關閉(S3C44B0X的Cache是指令與資料合一的,因此只能選擇關閉)。 C代碼調用Kernel的示例如下,其中r0和r1的值通過參數傳遞: void (*CallKernel)(int zero, int mach) = (void (*)(int, int))KERNEL_ADDR; CallKernel(0, 178); 5.輔助功能 完整的Bootloader還應該支援從主機下載檔到目標板的RAM;用RAM中的資料燒寫Flash Memory;以及上述功能所需的人機交互介面。檔下載途徑視目標板所提供的物理通訊介面而定,比較簡單的方法一般是通過串口,用Xmodem或 Ymodem協定下載,但速度較慢。目標板上只需要實現協議的接收部份,主機上可以用HyperTerminal等工具來發送檔。如果目標板提供 Ethernet等快速介面,也可以移植一個簡單的TCP/IP棧,用TFTP等標準檔傳輸協議下載。目前Flash Memory一般都是用NOR Flash,燒寫是非常簡單的;需要注意的是對於多數Flash晶片,在Erase/Program之前需要先Unprotect。人機交互介面不難在串 列終端上實現,這裏就不贅述了。 二. uClinux Kernel的調試 在移植uClinux的過程中,較為困難的是如何發現並解決Kernel啟動階段的問題。本節的內容主要取自網上的一篇文檔《用AXD + Multi-ICE調試uClinux內核》,說明如何使用ADS和Multi-ICE工具對Kernel進行源代碼級的調試。 首先是設置arm-elf-gcc編譯器,使其能夠輸出dwarf-2格式的調試資訊。修改linux/Makefile,將CFLAGS_KERNEL設置為-gdwarf-2,然後重新編譯uClinux Kernel: CFLAGS_KERNEL = -gdwarf-2 把以下檔複製到Windows下:image.ram,System.map,linux以及一份編譯時所用的內核源碼。image.ram是上面提到過 的非壓縮/非XIP模式的Kernel Image;System.map提供了各symbol所對應的地址;linux是一個ELF格式的檔,包含有AXD Debugger所需的調試資訊。 開發板上電,啟動Multi-ICE Server,啟動AXD Debugger。使用File?Load Memory From File讀入image.ram,其中基底位址應設為Kernel的TEXTADDR,這裏是0x0C200000。然後使用File?Load Debug Symbols讀入linux檔。把PC寄存器設置為image.ram的起始位址0x0C200000,此時在AXD Debugger的Disassembly視窗中已能看到一些符號的名稱。 在AXD Debugger中,Processor Views?Source,選擇要調試的源碼檔。例如,選擇linux/init/main.c,在Kernel的入口函數start_kernel() 中設置中斷點。也可以在System.map中查找到start_kernel的位址,然後在Disassembly視窗中跳到該位址並設置中斷點。最後 Go,就可以從中斷點處開始調試了。 三. uClinux Kernel的移植 1. 相關的目錄結構 Machine-Specific源代碼: linux/arch/armnommu/mach-s3c44b0 Machine-Specific頭文件: linux/include/asm-armnommu/arch-s3c44b0 通用的設備驅動程式: linux/drivers/block linux/drivers/char linux/drivers/mtd linux/drivers/net 檔系統: linux/fs 網路協定棧: linux/net 其他linux/arch/armnommu 子目錄下的Architecture-Specific源代碼: kernel: 核心內核代碼; mm: 記憶體管理代碼; lib: ARM-Specific或經過優化的內部庫函數代碼; nwfpe/fastfpe: 浮點庫的兩種實現; boot: 用於生成壓縮內核鏡像的代碼; tools: 用於自動生成頭檔的配置腳本; def-configs: 各Machine-Specific的缺省配置檔。 其他linux/include/asm-armnommu子目錄下的Architecture-Specific頭文件: hardware: ARM-Specific晶片或設備的頭檔; mach: 多數Machine共有的一般性介面定義(如DMA/IRQ/PCI); proc-armo: 26-bit版本的ARM處理器相關頭檔; proc-armv: 32-bit版本的ARM處理器相關頭檔。 2. 相關的源碼檔和移植要點 現在結合源碼檔,對一些移植過程中的要點進行討論,目的是得到能夠在Samsung S3C44B0X開發板上運行的非壓縮,非XIP模式的uClinux Kernel Image。該Image由Bootloader載入並調用。 2.1 內核配置系統 Linux的內核配置系統由Makefile,配置腳本(config.in)和配置工具組成。當用戶Make config,Make menuconfig或Make xconfig時,相應的配置工具按照配置腳本config.in的內容顯示可用的配置選項。用戶完成配置並存檔退出時,配置資訊保存在配置檔. config中,原有的.config文件被更名為.config.old。Makefile根據.config中的配置資訊,構造出需要編譯的原始檔案 列表,然後分別編譯;並根據Makefile中指定的鏈結器腳本,把目標代碼鏈結到一起,最終形成Linux Kernel Image。 在配置腳本linux/arch/armnommu/config.in中,應給出可供用戶選擇S3C44B0X開發板的選項,並定義基於該處理器的開發板的一些重要參數,如記憶體空間等: comment 'System Type' choice 'ARM system type' …… …… S3C44B0 CONFIG_ARCH_S3C44B0 …… …… if [ "$CONFIG_ARCH_S3C44B0" = "y" ]; then define_bool CONFIG_NO_PGT_CACHE y define_bool CONFIG_CPU_32 y define_bool CONFIG_CPU_26 n define_bool CONFIG_CPU_ARM710 y define_bool CONFIG_CPU_WITH_CACHE y define_bool CONFIG_CPU_WITH_MCR_INSTRUCTION n define_bool CONFIG_SERIAL_S3C44B0 y define_hex DRAM_BASE 0x0C000000 define_hex DRAM_SIZE 0x02000000 define_hex FLASH_MEM_BASE 0x00000000 define_hex FLASH_SIZE 0x00200000 fi 在針對ARM Architecture (NO MMU)的linux/arch/armnommu/Makefile中,應為S3C44B0X開發板定義處理器類型(26-bit或32-bit), Machine名稱和代碼段基底位址,並指定鏈結器腳本為linux/arch/armnommu/vmlinux-armv.lds.in: LINKFLAGS := -p -X -T arch/armnommu/vmlinux.lds …… ifeq ($(CONFIG_CPU_32),y) PROCESSOR = armv endif …… ifeq ($(CONFIG_ARCH_S3C44B0), y) TEXTADDR = 0x0C200000 MACHINE = s3c44b0 endif …… arch/armnommu/vmlinux.lds: arch/armnommu/vmlinux-$(PROCESSOR).lds.in dummy 2.2 內核啟動入口 在linux/Makefile中可以找到生成uClinux Kernel Image的規則為: ifdef CONFIG_UCLINUX LINUX=linux endif …… $(LINUX): $(CONFIGURATION) init/main.o init/version.o init/do_mounts.o linuxsubdirs $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o init/do_mounts.o --start-group $(CORE_FILES) $(DRIVERS) $(NETWORKS) $(LIBS) --end-group -o $(LINUX) 可見linux是由HEAD、main.o、version.o、do_mounts.o、CORE_FILES、DRIVERS、NETWORKS和 LIBS組成的。這些變數定義了用於鏈結生成linux的目標檔和庫檔列表。其中HEAD在linux/arch/armnommu/Makefile中 定義,用來確定最先被鏈結進linux的檔: HEAD := arch/armnommu/kernel/head-$(PROCESSOR).o arch/armnommu/kernel/init_task.o 因此,可以確定入口代碼是linux/arch/armnommu/kernel/head-armv.S。在前面介紹Bootloader時曾經提到, uClinux Kernel在啟動時要求把Machine Type ID存放在寄存器r1中。如果Bootloader在調用Kernel時沒有以參數傳遞的方式設置r1,則必須在head-armv.S中先對r1進行初 始化: …… #elif defined(CONFIG_ARCH_S3C44B0) mov r1, #MACH_TYPE_S3C44B0 …… #endif 其中,宏MACH_TYPE_S3C44B0定義在頭檔linux/include/asm-armnommu/mach-types.h中: #define MACH_TYPE_S3C44B0 178 …… #ifdef CONFIG_ARCH_S3C44B0 #ifdef machine_arch_type #undef machine_arch_type #define machine_arch_type __machine_arch_type #else #define machine_arch_type MACH_TYPE_S3C44B0 #endif #define machine_is_s3c44b0() (machine_arch_type==MACH_TYPE_S3C44B0) #else #define machine_is_s3c44b0() (0) #endif 該頭檔是由腳本linux/arch/armnommu/tools/gen-mach-types根據linux/arch/armnommu/tools/mach-types檔中的對應資訊自動生成的: #machine_is_xxx CONFIG_xxxx MACH_TYPE_xxx number …… …… …… …… s3c44b0 ARCH_S3C44B0 S3C44B0 178 在head-armv.S中,接下來就可以初始化Processor ID和Machine Type ID,清除BSS段,並跳轉到C代碼的入口函數start_kernel()了: #if defined(CONFIG_ARCH_S3C44B0) adr r5, LC0 ldmia r5, {r5, r6, r8, r9, sp} /* clear BSS */ mov r4, #0 1: cmp r5, r8 strcc r4, [r5], #4 bcc 1b ldr r2, S3C44B0_PROCESSOR_TYPE str r2, [r6] mov r2, #MACH_TYPE_S3C44B0 str r2, [r9] /* call start_kernel() */ mov fp, #0 b start_kernel LC0: .long __bss_start @ r5 .long processor_id @ r6 .long _end @ r8 .long __machine_arch_type @ r9 .long init_task_union+8192 @ sp S3C44B0_PROCESSOR_TYPE: .long 0x36366036 #endif 其中標號__bss_start和_end分別代表了BSS段的起始地址和結束地址,它們都定義在鏈結器腳本 linux/arch/armnommu/vmlinux-armv.lds.in中。而總體變數processor_id和 __machine_arch_type則是定義在linux/arch/armnommu/kernel/setup.c中。 2.3 異常處理和中斷處理 uClinux Kernel的C代碼入口函數start_kernel()定義在linux/init/main.c中。該函數中,有關調用setup_arch()處理內核啟動參數等內容在前面已有提及。此外,較為重要的還有對異常和中斷的處理: asmlinkage void __init start_kernel(void) { …… trap_init(); init_IRQ(); …… } linux/arch/armnommu/kernel/traps.c中的trap_init()函數調用 linux/arch/armnommu/kernel/entry-armv.S中的__trap_init()函數,把二級Exception Vector Table設置到基底位址vectors_base()處: void __init trap_init(void) { …… extern void __trap_init(void *); __trap_init((void *)vectors_base()); …… } 其中巨集vectors_base()被設置為系統中SDRAM的起始位址0x0C000000,定義在linux/include/asm-armnommu/proc-armv/system.h中: #ifdef CONFIG_ARCH_S3C44B0 #define vectors_base() (0x0C000000) #endif __trap_init()函數首先從標號.LCvectors處複製二級Exception Vector Table,其中參數vectors_base()通過寄存器r0傳遞: .equ __real_stubs_start, .LCvectors + 0x200 .LCvectors: swi SYS_ERROR0 b __real_stubs_start + (vector_undefinstr - __stubs_start) ldr pc, __real_stubs_start + (.LCvswi - __stubs_start) b __real_stubs_start + (vector_prefetch - __stubs_start) b __real_stubs_start + (vector_data - __stubs_start) b __real_stubs_start + (vector_addrexcptn - __stubs_start) b __real_stubs_start + (vector_IRQ - __stubs_start) b __real_stubs_start + (vector_FIQ - __stubs_start) …… …… …… adr r1, .LCvectors ldmia r1, {r2, r3, r4, r5, r6, r7, r8, r9} stmia r0, {r2, r3, r4, r5, r6, r7, r8, r9} 然後__trap_init()函數把各Exception Handler Routine的代碼從__stubs_start複製到vectors_base()+0x200,也就是0x0C000200處,以便與上述二級 Exception Vector Table中的跳轉指令相匹配。原先存放該段代碼的地址區間由__stubs_start和__stubs_end標記: add r2, r0, #0x200 adr r0, __stubs_start adr r1, __stubs_end 1: ldr r3, [r0], #4 str r3, [r2], #4 cmp r0, r1 blt 1b 中斷的初始化函數init_IRQ()定義在linux/arch/armnommu/kernel/irq.c中: struct irqdesc irq_desc[NR_IRQS]; void (*init_arch_irq)(void) __initdata = NULL; …… void __init init_IRQ(void) { …… for (irq = 0; irq < NR_IRQS; irq++) { irq_desc[irq].probe_ok = 0; irq_desc[irq].valid = 0; irq_desc[irq].noautoenable = 0; irq_desc[irq].mask_ack = dummy_mask_unmask_irq; irq_desc[irq].mask = dummy_mask_unmask_irq; irq_desc[irq].unmask = dummy_mask_unmask_irq; } init_arch_irq(); …… } irq_desc[]陣列用於存放IRQ請求描述符。每個中斷號對應一個irq_desc結構,NR_IRQS代表中斷總數。S3C44B0X開發板共有 26個中斷,中斷號和NR_IRQS都定義在linux/include/asm-armnommu/arch-s3c44b0/irqs.h中。函數指 標init_arch_irq()初始時為空,在前面提到的linux/arch/armnommu/kernel/setup.c裏的 setup_arch()函數中初始化: struct machine_desc *mdesc; …… mdesc = setup_architecture(machine_arch_type); …… init_arch_irq = mdesc->init_irq; 其中結構類型machine_desc定義在linux/include/asm-armnommu/mach/arch.h中,而對應的結構體定義在前面提到過的linux/arch/armnommu/mach-s3c44b0/arch.c中: MACHINE_START(S3C44B0, "44B0EVAL") MAINTAINER("XXX YYY") BOOT_MEM(DRAM_BASE,0x00000000,0x00000000) BOOT_PARAMS(0x0C000100) INITIRQ(genarch_init_irq) MACHINE_END setup_architecture()函數根據Machine Type ID在.arch.info段中搜索與之匹配的machine_desc結構,故mdesc->init_irq實際上將指向 genarch_init_irq()函數。該函數定義在linux/arch/armnommu/kernel/irq-arch.c中: void __init genarch_init_irq(void) { irq_init_irq(); } 而真正對irq_desc[]陣列進行初始化的irq_init_irq()函數定義在linux/include/asm-armnommu/arch -s3c44b0/irq.h中,該頭文件被linux/arch/armnommu/mach-s3c44b0/irq.c所包含:#include 在s3c44b0x_int_init()函數中將初始化S3C44B0X中斷控制器的各寄存器。s3c44b0x_int_init()、 s3c4510b_mask_ack_irq()、s3c4510b_mask_irq()和s3c4510b_unmask_irq()等函數也都定義 在linux/arch/armnommu/mach-s3c44b0/irq.c中。 static __inline__ void irq_init_irq(void) { …… s3c4510b_int_init(); …… for (irq = 0; irq < NR_IRQS; irq++) { irq_desc[irq].valid = 1; irq_desc[irq].probe_ok = 1; irq_desc[irq].mask_ack = s3c4510b_mask_ack_irq; irq_desc[irq].mask = s3c4510b_mask_irq; irq_desc[irq].unmask = s3c4510b_unmask_irq; } } 2.4 系統時鐘 在linux/init/main.c有對系統時鐘的初始化: asmlinkage void __init start_kernel(void) { …… time_init(); …… } 函數time_init()定義在linux/arch/armnommu/kernel/time.c中: #include void __init time_init(void) { …… setup_timer(); …… } 函數setup_timer()屬於architecture-specific代碼,可以在頭檔linux/include/asm-armnommu/arch-s3c44b0/time.h裏實現: extern struct irqaction timer_irq; extern void samsung_timer_interrupt(int, void *, struct pt_regs *); void __inline__ setup_timer (void) { rTCON &= 0xf0ffffff; rTCFG0 &= 0xff00ffff; rTCFG0 |= (16-1)<<16; rTCFG1 &= 0xff0fffff; rTCFG1 |= 0<<20; rTCNTB5 = fMCLK_MHz/(100*16*2); rTCON |= 0x02000000; rTCON &= 0xf0ffffff; rTCON |= 0x05000000; CLEAR_PEND_INT(IRQ_TIMER); INT_ENABLE(IRQ_TIMER); timer_irq.handler = samsung_timer_interrupt; setup_arm_irq(IRQ_TIMER, &timer_irq); } 該函數首先初始化S3C44B0X PWM Timer 5,作為系統時鐘源。Timer 5的輸入時鐘頻率由以下公式計算: Timer Input Clock Frequency = fMCLK_MHz / (Prescaler Value + 1) / (Divider Value) 其中Prescaler Value和Divider Value在TCFG0和TCFG1寄存器中設置。fMCLK_MHz通常等於PLL的輸出頻率,定義在linux/include/asm- armnommu/arch-s3c44b0/hardware.h中。PLL是在Bootloader裏初始化的,該頻率值應與當初的設置相匹配,假設 為50MHz: #define MHz 1000000 #define fMCLK_MHz (50 * MHz) 對寄存器TCNTB5的設置決定了uClinux系統時鐘的頻率,這裏為100Hz,即Timer 5的計時週期為(1/100)秒。然後通過設置TCON寄存器,更新TCNTB5的值,並啟動Timer 5計時器,使其工作在Auto Reload模式下。 setup_timer()函數最後清除中斷標記,開Timer 5中斷,掛接中斷服務常式samsung_timer_interrupt(),並通過 linux/arch/armnommu/kernel/irq.c中的setup_arm_irq()函數用已經賦過初值的timer_irq結構來初 始化Timer 5的中斷處理資料結構。samsung_timer_interrupt()函數定義在linux/arch/armnommu/mach- s3c44b0/time.c中,只是簡單地調用linux/kernel/timer.c中的do_timer()函數: void samsung_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) { do_timer(regs); } http://people.ofset.org/~ckhung/b/sa/cygwin.php |
|
( 知識學習|隨堂筆記 ) |