Bootloader初始化启动过程分析

来源:互联网 发布:容忍与自由知乎 编辑:程序博客网 时间:2024/04/30 03:34

    今天我们讨论一下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》
 
屏蔽硬件中断
ldr     r12, =INTERRUPT_CONTROL_BASE
        ldr     r0, =0x00000000
        str     r0, [r12, #ICMR]
        str     r0, [r12, #ICLR]
这一部分的代码很简单即使将中断屏蔽寄存器ICMR0屏蔽所有硬件中断。
 
初始化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寄存器,有几个问题要注意:
静态存储器空间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 Developers 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_startld_text_and_data_size两个变量的含义,要理解这两个变量,我们首先要看看ld-xscale文件的内容和作用。
内核可执行文件由许多链接在一起的对象文件组成。对象文件有许多节,如文本、数据、init数据、bss等等。这些对象文件都是由一个称为链接器脚本的文件链接并装入的。这个链接器脚本的功能是将输入对象文件的各节映射到输出文件中;换句话说,它将所有输入对象文件都链接到单一的可执行文件中,将该可执行文件的各节装入到指定地址处。ld-xscalebootloader链接器脚本,它负责链接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中。
  
    这篇文章讲述了bootloader的启动代码中对系统的设置过程。我们可以看到,即便是一个简单的start_xscale.S的汇编文件,要理解它也是不容易的,需要具有对PXA255处理器比较深刻的理解,而对硬件的理解,是嵌入式系统开发的基础,我想,这可能是比熟悉操作系统或bootloader这类系统软件更加重要的东西。好了,这篇文章先写到这里,接着,我们会讲述Linux操作系统在PXA255上的移植过程,呵呵,这也需要一定的硬件知识。

原创粉丝点击