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恢复中断前的程序位置下一条指令