Bootloader初始化启动过程分析【转】

作者:谷丰,您可以通过gufeng77@126.com和他取得联系。转载请包含上述内容。 

今天我们讨论一下PXA255芯片的bootloader的初始化过程,也就是start_xscale.S的汇编文件中包含的内容。E1开发板的硬件配置是这样的,400M Turbo模式运行的PXA255处理器,32M Flash和64M SDRAM。start_xscale.S包含的主要内容是系统上电后的初始化过程,依次为:屏蔽硬件中断、初始化GPIO引脚、初始化Flash和SDRAM、拷贝bootloader代码到SDRAM中、拷贝内核代码到SDRAM中、最后跳转到bootloader的main程序中去执行。关于PXA255芯片的详细信息请参阅《PXA255 Developer’s Manual》。 

1 屏蔽硬件中断 
ldr     r12, =INTERRUPT_CONTROL_BASE 
        ldr     r0, =0x00000000 
        str     r0, [r12, #ICMR] 
        str     r0, [r12, #ICLR] 
这一部分的代码很简单,即使将中断屏蔽寄存器ICMR置0,屏蔽所有硬件中断。 

2 初始化GPIO引脚 
gpio_init: 
ldr     r12, =GPIO_BASE 

   ldr     r0, =GAFR0L_VALUE 
    str     r0, [r12, #GAFR0_L] 
    ldr     r0, =GAFR0U_VALUE 
    str     r0, [r12, #GAFR0_U] 

    ldr     r0, =GAFR1L_VALUE 
    str     r0, [r12, #GAFR1_L] 
    ldr     r0, =GAFR1U_VALUE 
    str     r0, [r12, #GAFR1_U] 

    ldr     r0, =GAFR2L_VALUE 
    str     r0, [r12, #GAFR2_L] 
    ldr     r0, =GAFR2U_VALUE 
    str     r0, [r12, #GAFR2_U] 

    ldr     r0, =GPSR0_VALUE 
    str     r0, [r12, #GPSR0] 
    ldr     r0, =GPSR1_VALUE 
    str     r0, [r12, #GPSR1] 
    ldr     r0, =GPSR2_VALUE 
    str     r0, [r12, #GPSR2] 

    ldr     r0, =GPCR0_VALUE 
    str     r0, [r12, #GPCR0] 
    ldr     r0, =GPCR1_VALUE 
    str     r0, [r12, #GPCR1] 
    ldr     r0, =GPCR2_VALUE 
    str     r0, [r12, #GPCR2] 

    ldr     r0, =GPDR0_VALUE 
    str     r0, [r12, #GPDR0] 
    ldr     r0, =GPDR1_VALUE 
    str     r0, [r12, #GPDR1] 
    ldr     r0, =GPDR2_VALUE 
    str     r0, [r12, #GPDR2] 

// Clear the peripheral control register bits, so that we can use gpio as configured above 
   ldr     r1, =PSSR 
   ldr     r2, =(PSSR_RDH | PSSR_PH) 
   str     r2, [r1] 
         
   mov     pc, lr 

这里我们着重讨论关于GPIO的功能寄存器的设置,也就是GAFR寄存器,有几个问题要注意: 
1 静态存储器空间nCS0的片选信号没有与GPIO脚复用,,这是因为由于芯片当复位时自动跳转到地址0运行,因此对这一块地址需要专用的片选信号。其它静态存储器空间(nCS[1:5])的片选信号都与GPIO脚复用,即也可用做一般功能的GPIO,当系统有外部设备需要访问时(如FLASH、具有FIFO的专用芯片等),可以将该块地址的片选用对应GPIO引脚来代替。 
2 GPIO[18]被设置为RDY信号输入,如果芯片外接VLIO设备的话,需要用到该信号。 
该段代码最后通过向PSSR寄存器的RDH和PH位写1来清零该位,这时候GPIO才可以按照上面的配置进行工作。 

3初始化存储器 
3.1 下面我们开始讨论Flash和SDRAM的初始化,在配置这二者之前,首先要做一些系统时钟方面的处理。 
init_sdram: 
mov    r10, lr 

    ldr     r12, =CLOCK_MANAGER_BASE 

    ldr     r0, =CKEN_VALUE 
    str     r0, [r12, #CKEN] 
    ldr     r0, =OSCC_VALUE //The 32.768k oscillator clocks the RTC and PM. 
    str     r0, [r12, #OSCC] 
上面的代码首先配置CKEN寄存器,屏蔽除了FFUART之外所有设备的时钟信号,FFUART之所以被使能是因为现在需要串口工作。接着代码配置OSCC寄存器,选择32.768K的晶振作为RTC和Power_Manager的同步信号。

3.2 
#if 1 
    ldr r0, =CCCR_VALUE 
    str r0, [r12, #CCCR] 
    mov r1, #3 
    mcr p14, 0, r1, c6, c0, 0 //Enter Frequency Change Sequence, turbo mode is set at the same time 

//set OS count value to 0 
ldr r1, =OSCR      
    ldr r0, =0 
    str r0, [r1] 

    ldr r0, =0x300      //wait for 0x300 OS counts 
wait_for_clock: 
    ldr r2, [r1] 
    cmp r0, r2 
    bne wait_for_clock 
#endif 
代码接着配置CCCR寄存器,也就是设置我们常说的L,M,N值,分别设置为27,2,2,这样配置完毕后的各频率值为: 
内存频率 = 3.6864 x 27 = 100MHz 
运行模式频率 = 内存频率 x 2 = 200MHz 
Turbo模式频率 = 运行模式频率 x 2 =400MHz 
接下来通过设置协处理器14的寄存器6—CCLKCFG,将处理器模式设置为Turob模式,然后进入频率改变时序,最后等待0x300个OS记数直到频率设置成功。 

3.3 
//Step 1 in Intel's code 
    ldr     r12, =MEM_CTL_BASE 

    ldr     r0, =MSC0_VALUE 
    str     r0, [r12, #MSC0] 

    ldr     r0, =MSC1_VALUE 
    str     r0, [r12, #MSC1] 
    ldr     r0, [r12, #MSC1] 

    ldr     r0, =MSC2_VALUE 
    str     r0, [r12, #MSC2] 
    ldr     r0, [r12, #MSC2] 

    ldr     r0, =MECR_VALUE 
    str     r0, [r12, #MECR] 

    ldr     r0, =MCMEM0_VALUE 
    str     r0, [r12, #MCMEM0] 

    ldr     r0, =MCMEM1_VALUE 
    str     r0, [r12, #MCMEM1] 

    ldr     r0, =MCATT0_VALUE 
    str     r0, [r12, #MCATT0] 

    ldr     r0, =MCATT1_VALUE 
    str     r0, [r12, #MCATT1] 

    ldr     r0, =MCIO0_VALUE 
    str     r0, [r12, #MCIO0] 

    ldr     r0, =MCIO1_VALUE 
str     r0, [r12, #MCIO1] 
接下来代码配置Flash和16位PC Card接口参数,其中的MSC[0:2]寄存器包含Flash的配置参数,剩下的寄存器配置16位PC Card。其中对Flash的配置设计到几个关键的参数,分别是: 

通过查看CPU外接的Flash类型的具体资料,配置以上参数。 

3.4 下面的代码主要配置SDRAM的参数,也是bootloader中比较复杂的地方。 
ldr     r0, =MDREFR_VALUE 
ldr     r3, [r12, #MDREFR] 
    ldr     r1, =0xFFF 
    and     r0, r0, r1 

   // Make the DRI we read from MDREFR what MDREFR_VALUE says it is. 
   // We also Free KXFREE the free running bits. 
    bic     r3, r3, r1 
    bic    r3, r3, #0x03800000 
    orr     r3, r3, r0 

        //Write it back 
str     r3, [r12, #MDREFR] 
首先从MDREFR寄存器中得到当前值,在此基础上设置DRI(SDRAM刷新间隔)值,关于DRI值的计算在《PXA255 Develop’s Manual》中是这样的: 
(Refresh time / rows) x Memory clock frequency / 32. 
目前公认的标准是,存储体中电容的数据有效保存期上限是64ms,也就是说每一行刷新的循环周期是64ms。这样刷新速度就是:64ms/行数量。由于行地址有13位(在MDCNFG寄存器的DRAC0[1:0]中设置,需要与硬件一致),所以每行的的刷新时间为64ms/213=64ms/8192 = 7.8125μs,那么7.8125μs x 100MHz / 32 = 0x018,这样就得到了系统的DRI值。 
然后代码将MDREFR:KxFree位清零。 

ldr r0, =MDREFR_VALUE 
    ldr r1, =0xF6000 // Mask of SDCLK's settings minus EXPIN 
    and r0, r0, r1 
    bic r3, r3, r1 
    orr r3, r3, r0 

    str r3, [r12, #MDREFR] 
    ldr r3, [r12, #MDREFR] 
由于板子上没有使用同步SRAM,因此不需要配置SXCNFG寄存器。这段代码设置MDREFR寄存器的KxRUN和KxDB2位,禁用SDCLK0,SDCLK2,只使能SDCLK1,这是因为参照《PXA255 Developer’s Manual》文档说明,SDCLK0被分配给SRAM,SDCKL1和SDCLK2分别被SDRAM区域的Bank[0,1]和Bank[2,3]使用,由于我们的PXA255板子上面使用异步Flash,同时SDRAM区域只有Bank0上贴了64M,所以只需要使能SDCLK1信号。接着代码将SDCLK1的频率设置为1/2内存频率。 

bic     r3, r3, #0x00400000 
str     r3, [r12, #MDREFR] 
禁止自刷新模式(Self-Refresh)。 
这里我们讨论一下SDRAM的刷新。刷新操作分为两种:自动刷新(Auto Refresh,简称AR)与自刷新(Self Refresh,简称SR)。不论是何种刷新方式,都不需要外部提供行地址信息,因为这是一个内部的自动操作。SR则主要用于休眠模式低功耗状态下的数据保存,这方面最著名的应用就是STR(Suspend to RAM,休眠挂起于内存)。在发出AR命令时,将CKE置于无效状态,就进入了SR模式,此时不再依靠系统时钟工作,而是根据内部的时钟进行刷新操作。在SR期间除了CKE之外的所有外部信号都是无效的(无需外部提供刷新指令),只有重新使CKE有效才能退出自刷新模式并进入正常操作状态。

ldr  r0, =MDREFR_VALUE 
    ldr  r1, =0x03809000 
    and  r0, r0, r1 
    orr  r3, r3, r0 
    str    r3, [r12, #MDREFR] 
    nop 
nop 
将SDCKE0禁用,将SDCKE1使能,原因与上面讨论的SDCLKx设置一样。 

ldr     r0, =MDCNFG_VALUE 

    //disable all sdram banks 
    bic     r0, r0, #0x00000003 
    bic     r0, r0, #0x00030000 

    //program banks 0/1 for 32 bit bus width 
    bic     r0, r0, #0x00000004 

   // test with 16 bit bus width 
// orr r0, r0, #0x00000004 

    //write MDCNFG, without enabling SDRAM banks 
    str     r0, [r12, #MDCNFG] 

    //Step 5 in Intel's code 
    ldr     r0, =OSCR 
mov r1, #0 
    str r1, [r0] 

    //pause for approx 200 usecs 
    ldr     r4, =0x300 
sdram_dly: 
    ldr     r1, [r0] 
    cmp     r4, r1 
bgt     sdram_dly 
上面的代码配置MDCNFG内容,首先将SDRAM的4个分区(Bank)屏蔽,数据宽度为32位。另外配置了外接SDRAM芯片的与CAS潜伏期有关的参数,这些参数包括: 
CL:在选定列地址(CAS有效)后,就已经确定了具体的存储单元,剩下的事情就是数据通过数据I/O通道(DQ)输出到内存总线上了。但是在CAS发出之后,仍要经过一定的时间才能有数据输出,从CAS与读取命令发出到第一笔数据输出的这段时间,被定义为CL(CAS Latency,CAS潜伏期)。由于CL只在读取时出现,所以CL又被称为读取潜伏期(RL,Read Latency)。 
tRP: 在发出预充电命令之后,要经过一段时间才能允许发送RAS行有效命令打开新的工作行,这个间隔被称为tRP(Precharge command Period,预充电有效周期)。 
tRCD: 在发送列读写命令时必须要与行有效命令有一个间隔,这个间隔被定义为tRCD,即RAS to CAS Delay(RAS至CAS延迟),也可以理解为行选通周期。 
tRAS: (Active to Precharge Command,行有效至预充电命令间隔周期)。 
tRC: 包括行单元开启和行单元刷新在内的整个过程所需要的时间,(Row Cycle Time,SDRAM行周期时间) 。 
以上各参数单位为内存时钟周期数。 

//turn everything off 
    mov     r0, #0x78 
    mcr     p15, 0, r0, c1, c0, 0 
    //Access memory that has not been enabled for CBR refresh cycles (8) 
Ldr    r0, =SDRAM_BASE 
    str     r0, [r0] 
    str     r0, [r0] 
    str     r0, [r0] 
    str     r0, [r0] 
    str     r0, [r0] 
    str     r0, [r0] 
    str     r0, [r0] 
    str     r0, [r0] 
这段代码首先将通过协处理器15,将片内数据缓冲禁用,同时禁用burst模式读写数据。禁用缓冲主要是为了使下面数据的写操作可以按顺序依次执行。 
接着代码进行8次数据的写操作,这导致8次芯片的CBR刷新,即上面提到的AR刷新。接着,用户可以自己的需要再将数据缓冲使能,这里我们没有进行该使能操作。 

ldr     r0, [r12, #MDCNFG] 

    //enable bank 0 (what about bank 1?) 
    orr     r0, r0, #0x00000001 
    str     r0, [r12, #MDCNFG] 

    //write MDMRS again 
    ldr     r0, =MDMRS_VALUE 
str     r0, [r12, #MDMRS] 
接着代码使能SDRAM的Bank0,然后配置MDMRS寄存器。 
在SDRAM芯片内部还有一个逻辑控制单元,并且有一个模式寄存器为其提供控制参数。因此,每次开机时SDRAM都要先对这个控制逻辑核心进行初始化,也就是配置MDMRS寄存器。寄存器的信息由地址线来提供。在设置到模式寄存器之后,芯片就开始了进入正常的工作状态, 

ldr     r0, [r12, #MDREFR] 
    ldr    r11, =0xFFEFFFFF 
and     r0, r0, r11 
str     r0, [r12, #MDREFR] 
mov     pc, r10 
清空APD位。 

至此,完成CPU的存储器接口的配置工作。 

4 bootloader从Flash到SDRAM的拷贝

copy_to_ram: 
mov r8, lr 

ldr    r0, =0 
    ldr    r1, =_ld_text_start 
    ldr    r2, =_ld_text_and_data_size 

copy_loop: 
ldr    r3, [r0] 
    str    r3, [r1] 
    add    r0, r0, #4 
    add    r1, r1, #4 
    subs    r2, r2, #4 
    bne    copy_loop 

    mov     pc, r8 
上面的代码完成将Bootloader程序从Flash到SDRAM的拷贝工作,之所以要进行该拷贝,是因为SDRAM的运行速度要远远高于Flash,因此加快了bootloader的运行速度。这里要注意的是ld_text_start和ld_text_and_data_size两个变量的含义,要理解这两个变量,我们首先要看看ld-xscale文件的内容和作用。 
内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init数据、bss等等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。ld-xscale就是bootloader的链接器脚本,它负责链接bootloader可执行程序的各个节并将它们装入内存中特定偏移量处。 
好了,了解了ld-xscale文件的作用之后,我们来看看其中和上面的代码有关的内容。 
.text 0xA4000000 - 0x80000:  
   { 
      _ld_text_start = .; 
      *(.text) 
      *(.got) 
      *(.got.pld) 
      *(.rodata) 
      _ld_text_end = .; 
   } 
。。。 
_ld_text_and_data_size = SIZEOF(.text) + SIZEOF(.data); 
这里代表的是bootloader可执行文件的代码段(text)的链接信息,很明显,_ld_text_and_data_size代表的是bootloader可执行程序的代码段和数据段加起来的总的程序大小。_ld_text_start代表的是代码段的起始地址,是0xA4000000 – 0x80000,为什么是这个地址呢?我们看看PXA255系统的内部映射表,0xA0000000到0xA4000000是SDRAM的Bank 0 的地址空间,也即是我们的64M的SDRAM的地址,这样我们就明白了,bootloader是要把自己从Flash中拷贝到SDRAM地址为0xA4000000 – 0x80000的地方运行,这样我们就分配了0x80000大小,即512K大小的bootloader的运行空间。那么是不是只要bootloader的程序大小小于这个空间,就可以这么分配呢?不是的,我们看到ld-xscale文件的最后一部分, 
_ld_stack_address = _ld_text_start + 0x80000; 
bootloader的堆栈的起始地址被定义在了 
_ld_text_start + 0x80000 = 0xA4000000的地方,由于在这里堆栈是向下增长的,所以512K大小的空间里包括了bootloader程序和堆栈两部分,只要这两部分的大小不超过512K,这样的分配就是合理的。 
    
5 
// Loading kernel image 
    ldr r4, =KERNEL_SRAM_BASE 
    ldr r5, =KERNEL_DRAM_BASE 
    ldr r6, =KERNEL_MAX_SIZE 
    add r6, r6, r4 
repeat: 
    ldmia   r4!, {r0-r3, r7-r10} 
    stmia   r5!, {r0-r3, r7-r10} 
    cmp     r4, r6 
    blt     repeat 


ldr sp, =_ld_stack_address 

// Jump to c_main 
ldr r0, =c_main 
    mov pc, r0 
上面的代码是bootloader启动的最后一部分,它首先将Linux的内核从Flash中拷贝到SDRAM中,原因与前面bootloader的拷贝一样,为了加快系统的运行。然后设置bootloader的堆栈首地址,最后跳转到bootloader的C代码程序中运行,在这里,程序的运行也从Flash中跳到了SDRAM中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值