cpu/arm920t/start.S程序步驟大致有以下幾個
1、設置中斷向量表
2、設置CPU模式為SVC32 mode並且關閉IRQ與FIQ中斷
3、關閉看門狗
4、屏蔽所有中斷
5、判斷程序是否在RAM中運行如果不是的話則先關閉MMU再則需要初始化RAM。
6、設置堆棧准備在C函數中運行了
7、 跳轉到C函數clock_init初始化系統時鍾
8、跳轉到C函數CopyCode2Ram將代碼拷貝到RAM中
9、清零BSS段
10、跳轉到_start_armboot運行,此時代碼已經在RAM中運行了
11、IRQ中斷與FIQ中斷發生后的上下文處理
1、cpu/arm920t/start.S文件中存放着S3C2440芯片(內核是ARM920T)的異常向量表(即異常入口地址),從S3C2440芯片手冊得知每當板子重新上電即芯片復位后pc的值總是指向0,所以在鏈接時存放在最前面的文件肯定是cpu/arm920t/start.S。
.globl _start _start: b reset //復位異常地址 ldr pc, _undefined_instruction //未定義異常地址 ldr pc, _software_interrupt //軟件中斷異常地址 ldr pc, _prefetch_abort //預取指異常地址 ldr pc, _data_abort //數據異常地址 ldr pc, _not_used //保留 ldr pc, _irq //IRQ中斷異常地址 ldr pc, _fiq //FIQ快速中斷異常地址 _undefined_instruction: .word undefined_instruction//0x20地址處存放着未定義異常跳轉地址 _software_interrupt: .word software_interrupt _prefetch_abort: .word prefetch_abort _data_abort: .word data_abort _not_used: .word not_used _irq: .word irq _fiq: .word fiq .balignl 16,0xdeadbeef //0x3c地址存放一個固定的標志表明前面的數據為中斷向量表占用
2、當復位異常出現后,程序執行b reset后設置CPU模式為SVC32 mode並且關閉IRQ與FIQ中斷,b指令為位置無關碼,它的尋址方式是先找到reset程序段所在地址然后計算出當前程序地址與reset段地址的偏移然后根據偏移值跳轉,這么做的目的是因為:假設開發板通過nand啟動的,那么一上電后硬件自動將nand內的前4K程序拷貝到s3c2440內部的ram中運行程序,這段程序的地址與鏈接地址不一致,當程序運行地址與鏈接地址不一致時只能使用位置無關碼進行跳轉
reset: /* * set the cpu to SVC32 mode //設置cpsr寄存器使CPU進入系統模式,並且關閉FIQ與IRQ */ mrs r0,cpsr bic r0,r0,#0x1f orr r0,r0,#0xd3 msr cpsr,r0
3、關閉看門狗
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) ldr r0, =pWTCON//pWTCON為看門狗寄存器地址,全部清0即關閉看門狗 mov r1, #0x0 str r1, [r0]//關閉看門狗
4、屏蔽所有中斷,S3C2440的中斷控制器可以控制多個中斷源,可以設置中斷優先級。通過選出優先級最高的中斷源送到ARM920T的IRQ或FIQ異常,最后通過IRQ或FIQ異常響應相應的中斷源
/* * mask all IRQs by setting all bits in the INTMR - default */ mov r1, #0xffffffff ldr r0, =INTMSK //INTMSK為中斷控制寄存器 str r1, [r0] //屏蔽所有without sub中斷源 # if defined(CONFIG_S3C2410) ldr r1, =0x3ff ldr r0, =INTSUBMSK //INTSUBMSK為中斷控制寄存器 str r1, [r0] //屏蔽所有with sub中斷源 # endif
5、判斷程序是否在RAM中運行如果不是的話則先關閉MMU再則需要初始化RAM。下面代碼注釋引自博客http://www.cnblogs.com/lifexy/p/7309791.html
#ifndef CONFIG_SKIP_LOWLEVEL_INIT //這個在100ask24x0里未定義 adr r0, _start /* r0 <- current position of code */ ldr r1, _TEXT_BASE /* test if we run from flash or RAM */ cmp r0, r1 /* don't reloc during debug */ blne cpu_init_crit //未在ram中運行,所以要初始化ram
cpu_init_crit: mov r0, #0 mcr p15, 0, r0, c7, c7, 0 //關閉ICaches(指令緩存,關閉是為了降低MMU查表帶來的開銷)和DCaches(數據緩存,DCaches使用的是虛擬地址,開啟MMU之前必須關閉) mcr p15, 0, r0, c8, c7, 0 //使無效整個數據TLB和指令TLB(TLB就是負責將虛擬內存地址翻譯成實際的物理內存地址) mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS) //bit8:系統不保護,bit9:ROM不保護,bit13:設置中斷向量表的位置為0x0~0x1c,即異常模式基地址為0X0 bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM) //bit0~2:禁止MMU,禁止地址對齊檢查,禁止數據Cache.bit7:設為小端模式 orr r0, r0, #0x00000002 @ set bit 2 (A) Align //bit2:開啟數據Cache orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache //bit12:開啟指令Cache mcr p15, 0, r0, c1, c0, 0 /* mcr/mrc: Caches:是一種高速緩存存儲器,用於保存CPU頻繁使用的數據。在使用Cache技術的處理器上,當一條指令要訪問內存的數據時, 首先查詢cache緩存中是否有數據以及數據是否過期,如果數據未過期則從cache讀出數據。處理器會定期回寫cache中的數據到內存。 根據程序的局部性原理,使用cache后可以大大加快處理器訪問內存數據的速度。 其中DCaches和ICaches分別用來存放數據和執行這些數據的指令 TLB:就是負責將虛擬內存地址翻譯成實際的物理內存地址,TLB中存放了一些頁表文件,文件中記錄了虛擬地址和物理地址的映射關系。 當應用程序訪問一個虛擬地址的時候,會從TLB中查詢出對應的物理地址,然后訪問物理地址。TLB通常是一個分層結構, 使用與Cache類似的原理。處理器使用一定的算法把最常用的頁表放在最先訪問的層次。 這里禁用MMU,是方便后面直接使用物理地址來設置控制寄存器 */ mov ip, lr //臨時保存當前子程序返回地址,因為接下來執行bl會覆蓋當前返回地址. bl lowlevel_init //跳轉到lowlevel_init(位於u-boot-1.1.6/board/100ask24x0/lowlevel_init.S) mov lr, ip //恢復當前返回地址 mov pc, lr //退出
.globl lowlevel_init lowlevel_init://配置存儲控制器 by andy /* memory control configuration */ /* make r0 relative the current location so that it */ /* reads SMRDATA out of FLASH rather than memory ! */ ldr r0, =SMRDATA ldr r1, _TEXT_BASE sub r0, r0, r1 //求出SMRDATA實際位於的地址,nand啟動的話起始是位於0地址的,而不是鏈接地址 ldr r1, =BWSCON /* Bus Width Status Controller */ add r2, r0, #13*4 0: ldr r3, [r0], #4 str r3, [r1], #4 cmp r2, r0 bne 0b /* everything is fine now */ mov pc, lr .ltorg /* the literal pools origin */ SMRDATA: .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28)) .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT) .word 0xb1 .word 0x30 .word 0x30
6、設置堆棧准備在C函數中運行了。在SDRAM中,代碼段位於0x3f80000以上,sp=_TEXT_BASE-CFG_MALLOC_LEN-CFG_GBL_DATA_SIZE-CONFIG_STACKSIZE_IRQ-CONFIG_STACKSIZE_FIQ-12
/* Set up the stack */ //設置堆棧需要放到前面,因為clock_init是C函數 stack_setup: ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot *///_TEXT_BASE之前為代碼段 sub r0, r0, #CFG_MALLOC_LEN /* malloc area *///減去堆區 sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo *///減去全局變量區 #endif #ifdef CONFIG_USE_IRQ sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)//減去IRQ與FIQ占用的棧區 #endif sub sp, r0, #12 /* leave 3 words for abort-stack *///再預留12字節sp為堆棧寄存器,堆棧為向下增長

7、 跳轉到C函數clock_init初始化系統時鍾bl clock_init,clock_init具體代碼如下:代碼注釋引自博客http://www.cnblogs.com/lifexy/p/7309791.html
void clock_init(void) {
//時鍾的寄存器地址只能在函數內部定義,因為代碼還不在鏈接地址處運行 S3C24X0_CLOCK_POWER *clk_power = (S3C24X0_CLOCK_POWER *)0x4C000000; //定義一個S3C24X0_CLOCK_POWER型結構體指針,clk_power->LOCKTIME=0x4C000000 if (isS3C2410) //isS3C2410為0,執行else {... ...} else { /* FCLK:HCLK:PCLK = 1:4:8 */ clk_power->CLKDIVN = S3C2440_CLKDIV; //S3C2440_CLKDIV=0X05 /* change to asynchronous bus mod *///C語言內嵌匯編 __asm__( "mrc p15, 0, r1, c1, c0, 0\n" /* read ctrl register */ "orr r1, r1, #0xc0000000\n" //使其從快總線模式改變為異步總線模式,在2440手冊上看到 "mcr p15, 0, r1, c1, c0, 0\n" /* write ctrl register */ :::"r1" // ); /* to reduce PLL lock time, adjust the LOCKTIME register */ clk_power->LOCKTIME = 0xFFFFFFFF; //PLL 鎖定時間計數寄存器 /* configure UPLL */ clk_power->UPLLCON = S3C2440_UPLL_48MHZ; //UCLK=48Mhz /* some delay between MPLL and UPLL */ delay (4000); //等待UCLK時鍾波形穩定 /* configure MPLL */ clk_power->MPLLCON = S3C2440_MPLL_400MHZ; //FCLK=400Mhz /* some delay between MPLL and UPLL */ delay (8000); //等待FCLK時鍾波形穩定 } }
8、跳轉到C函數CopyCode2Ram將代碼拷貝到RAM中,根據ATPCS匯編調用C語言的規則,C函數參數為R0、R1、R2,返回值存在R0中。
#ifndef CONFIG_SKIP_RELOCATE_UBOOT relocate: /* relocate U-Boot to RAM *///需要重定位代碼到ram中 adr r0, _start /* r0 <- current position of code */ ldr r1, _TEXT_BASE /* test if we run from flash or RAM */ cmp r0, r1 /* don't reloc during debug */ beq clear_bss //如果程序已經在ram中運行,那么不需要再拷貝代碼到ram中了 ldr r2, _armboot_start ldr r3, _bss_start sub r2, r3, r2 /* r2 <- size of armboot *///求出剩余代碼段大小 #if 1 bl CopyCode2Ram /* r0: source, r1: dest, r2: size */ //拷貝代碼到ram中 #else
int CopyCode2Ram(unsigned long start_addr, unsigned char *buf, int size) { unsigned int *pdwDest; unsigned int *pdwSrc; int i; if (bBootFrmNORFlash())//判斷是從nand啟動的還是從nor啟動的,如果條件成立,表示是從nor啟動的 { pdwDest = (unsigned int *)buf; pdwSrc = (unsigned int *)start_addr; /* 從 NOR Flash啟動 */ for (i = 0; i < size / 4; i++)//4字節的寫入,所以需要size / 4 by andy { pdwDest[i] = pdwSrc[i];//norfalsh可以在配置完內存管理單元后,讀數據可以像內存一樣操作 } return 0; } else { /* 初始化NAND Flash */ nand_init_ll(); /* 從 NAND Flash啟動 */ nand_read_ll_lp(buf, start_addr, (size + NAND_BLOCK_MASK_LP)&~(NAND_BLOCK_MASK_LP)); return 0; } }
int bBootFrmNORFlash(void) { volatile unsigned int *pdw = (volatile unsigned int *)0; unsigned int dwVal; /* * 無論是從NOR Flash還是從NAND Flash啟動, * 地址0處為指令"b Reset", 機器碼為0xEA00000B, * 對於從NAND Flash啟動的情況,其開始4KB的代碼會復制到CPU內部4K內存中, * 對於從NOR Flash啟動的情況,NOR Flash的開始地址即為0。 * 對於NOR Flash,必須通過一定的命令序列才能寫數據, * 所以可以根據這點差別來分辨是從NAND Flash還是NOR Flash啟動: * 向地址0寫入一個數據,然后讀出來,如果沒有改變的話就是NOR Flash */ dwVal = *pdw; *pdw = 0x12345678; if (*pdw != 0x12345678) { return 1;//從nor啟動 } else { *pdw = dwVal;//復原原先0地址處的數據 return 0;//從nand啟動 } }
9、清零BSS段,BSS段通常是指用來存放程序中未初始化的全局變量和靜態變量的一塊內存區域,BSS段一般存放在代碼段之后
clear_bss: ldr r0, _bss_start /* find start of bss segment */ ldr r1, _bss_end /* stop here */ mov r2, #0x00000000 /* clear */ clbss_l:str r2, [r0] /* clear loop... *///循環清0BSS段內內容 add r0, r0, #4 cmp r0, r1 ble clbss_l
10、跳轉到_start_armboot運行,此時代碼已經在RAM中運行了
ldr pc, _start_armboot//這里可以用ldr是因為鏈接地址處已經有程序了 _start_armboot: .word start_armboot//start_armboot值為鏈接地址+偏移量
11、IRQ中斷與FIQ中斷發生后的上下文處理
irq: /* add by www.100ask.net to use IRQ for USB and DMA *///進入下面一段程序時CPU處於IRQ模式 sub lr, lr, #4 @ the return address //返回地址 ldr sp, IRQ_STACK_START @ the stack for irq //堆棧切回IRQ的堆棧 stmdb sp!, { r0-r12,lr } @ save registers //保存當前寄存器內容到IRQ堆棧中 ldr lr, =int_return @ set the return addr //將lr值設為int_return ldr pc, =IRQ_Handle @ call the isr //調用IRQ_Handle函數 int_return: ldmia sp!, { r0-r12,pc }^ @ return from interrupt//IRQ_Handle返回后執行這條 //:所有寄存器出棧,pc恢復中斷前的程序位置下一條指令
