试图搞懂MDK程序下载到Flash(一)--Nand Flash启动流程,加载域和运行域

转载 2015年11月21日 11:02:51

NAND FLASH启动流程   

在这里我先以TQ2440的启动代码分析,因为手上有本书,反正Nand Flash启动流程都是一样的对于mini2440和TQ2440来说。TQ2440的启动代码部分如下:

    ;**********************************************************************************

    .....                                               ;38行之前的代码都是一些准备工作

    .....                                               ;如宏定义、符号导入之类的

    35               AREA     Init,CODE,READONLY

    36               ENTRY

    37      ResetEntry

    38                b               ResetHandled          ;程序执行代码的第一句 

    ....                                                                  ;跳转到标号ResetHandler处执行

    ....

    63       ResetHandler

    64                ldr             r0,=WTCON

    65               ldr              r0,=0x0

    .....

    .....

    106              ldr             r0,=BWSCON

    107              ldr             r0,[r0]

    108              ands         r0,r0,#6                      ;OM[1:0]!=0,NOR Flash 启动

    109              bne          copy_proc_beg           ;do not read nand flash

    110             adr           r0,ResetEntry              ;OM[1:0]==0,NAND Flash启动

    111             cmp          r0,#0                           ;if use Multi-ice

    112             bne           copy_proc_beg            ;do not read nand flash for boot

    113    nand_boot_beg

    114             bl            RdNF2SDRAM

    115             ldr           pc,=copy_proc_beg

    116    copy_proc_beg

    117             adr         r0,ResetEntry

    118             ldr          r2,BaseOfROM

    119             cmp       r0,r2

    120             ldreq      r0,TopOfRom

    121             beq        InitRam

    122             ldr          r3,TopOfRom

    123    0 

    124             ldmia      r0!,{r4-r7}

    125             stmia      r2!,{r4-r7}

    126             cmp       r2,r3

    127             bcc         %B0

 

    128             sub        r2,r2,r3

    129             sub        r0,r0,r2 

    130    InitRam

    131            ldr           r2,BaseOfBSS

    132            ldr           r3,BaseOfZero

    133    0

    134           cmp        r2,r3

    135           ldrcc        r1,[r0],#4

    136           strcc       r1,[r2],#4

    137           bcc         %B0

    138           mov        r0,#0

     139          ldr          r3,EndOfBSS

     140    1

     141          cmp       r2,r3

     142          strcc       r0,[r2],#4

      143         bcc        %B1

      ;安装中断向量表

      144         ldr         r0,=HandleIRQ

      145         ldr         r1,=IsrIRQ

      146         str          r1,[r0]

 

      147         b           Main

      148   InitStacks

      149                        ;堆栈初始化代码部分的开始行

       ....

      168                        ;堆栈初始化代码部分的结束行 

       ....

       ....

      231         END       ;启动代码结束

     现在开始分析代码:

    106              ldr             r0,=BWSCON                                                            

    107              ldr             r0,[r0]                                                                         

    108              ands         r0,r0,#6                      ;OM[1:0]!=0,NOR Flash 启动

    109              bne          copy_proc_beg           ;do not read nand flash           

    110             adr           r0,ResetEntry              ;OM[1:0]==0,NAND Flash启动

    111             cmp          r0,#0                           ;if use Multi-ice                           

    112             bne           copy_proc_beg            ;do not read nand flash for boot

     第106~112行才是选择从NAND FLASH或者NOR FLASH启动的关键。

     第106~107行将BWSCON寄存器里的数据读入寄存器r0。从S3C2440的数据手册上可以查到BWSCON寄存器第1、2位,反映了系统总线宽度的信息。

     第108行,and指令主要用于测试数据的某几位是0还是1,后面的s表示该指令的运算结果影响标志位。因此,该条指令主要是测试r0第1、2两位。当运算结果是0时,说明r0的第1、2位是0,即BWSCON寄存器的第1、2位是0,即系统是从Nand Flash启动的,则执行第110行程序;当运算结果不为0时,说明是从Nor Flash启动,在执行第109行程序。

    第109行,执行该条指令的前提是系统从Nor Flash启动,然后跳转到copy_proc_beg标号处执行,其功能是:实现代码从加载域到运行域的搬移工作,这也是启动代码的核心。关于加载域和运行域在后面会讲到。

   第110~112行,其中adr指令比较难理解,adr伪指令是将标号基于PC的相对地址加载到寄存器中,因为是将程序下载到了NADN FLASH的0地址处,所以ResetEntry的地址为0,所以最终结果就是r0=0。关于ldr与adr指令、相对地址与绝对地址请看博客:点此打开链接

   第112行,由上面的分析可以知道,该行指令不执行,执行第113行指令。

    113    nand_boot_beg                                            

    114             bl            RdNF2SDRAM                     

    115             ldr           pc,=copy_proc_beg              

     第113行,只是一个程序标号,没有实际的用处。

     第114行,调用C语言函数RdNF2SDRAM,将代码从NAND FLASH读入到内存中,这个函数是在C文件中定义的。

     第115行,用ldr指令将copy_proc_beg的绝对地址加载到程序计数器PC中,即跳转到了内存中去执行。

     到此为止,从NAND FLASH启动的流程已经完全讲清楚了,尽管我还是不”很清楚“,下面就结合图1详细分析从NAND FLASH启动总流程,做到真清楚!


 

    ① 系统上电后,S3C2440处理器通过硬件电路自动将NAND FLASH中的前4KB代码复制到内部的Stepping Stonezhong ,该Stepping Stone就是一块RAM,可以执行程序。

     ② 程序从Stepping Stone的0地址处开始执行第1条指令。

     ③ 程序执行到bl     RdNF2SDRAM(第114行)时,调用NAND FLASH函数,将NAND FLASH中的代码全部复制到SDRAM中。

     ④ 执行  ldr    pc,=copy_proc_beg,将copy_proc_beg的绝对地址加载到程序计数器PC中。

     ⑤ 程序跳转到SDRAM中copy_proc_beg标号处执行。

     注意:第(4)步中提到了copy_proc_beg的绝对地址,那么什么是绝对地址呢?绝对地址是程序编译链接后确定的,如图2所示

在链接时,指定的entry 地址是0x3000 0000,这个entry地址即第36行的ENTRY,也就是说,在最终编译链接后生成的可执行程序中,copy_proc_beg的绝对地址=0x3000 0000+偏移量。这个偏移量是多少呢?就是copy_proc_beg距离ENTRY的偏移量。因此,在第(4)步执行完后,虽然stepping stone中也有copy_proc_beg,SDRAM中也有copy_proc_beg,但是程序并没有跳到stepping stone中的copy_proc_beg处执行,而是跳到了SDRAM中的copy_proc_beg处去执行。

加载域和运行域、RW、RO、ZI

   一个简单的可执行程序的映像文件如图3所示。它由RO、RW、和ZI三个段组成,其中RO为代码段和只读数据段;RW为可读/写的数据段;ZI为未初始化的数据段。

   此外,映像文件还可以分为加载域(Load View)和运行域(Execution View)。加载域反映了ARM可执行映像文件各个段存放在存储器中时的位置关系,运行域反映了ARM可执行映像文件各个段真正执行时在存储器中的位置关系。

   前文讲到,从NOR FLASH启动后,当启动代码执行到第109行时,会跳转到copy_proc_beg处执行。此时,程序加载域和运行域的分布情况如图4所示

      加载域的起始地址是0x0000 0000,为什么呢?因为加载域反映了ARM可执行文件存放在存储器中时的位置关系,程序下载到NOR FLASH中,又知道NOR FLASH的起始地址是0x0000 0000,因此加载域的起始地址是0x0000 0000,在加载域中,首先存放RO,后面紧跟着存放的是RW,并没有ZI。RO为代码段,属性为只读;RW为已经初始化的全局变量段,属性可读、可写;ZI为未初始化的全局变量段。为什么在加载域中没有ZI呢,这主要是为了减小ARM可执行映像文件的容量,因为ZI中存放的是未初始化的全局变量,那么只需要记录该段的容量即可,在运行域中需要根据这个容量分配内存,然后用0填充。

      运行域的起始地址是0x3000 0000,这又是为什么呢?

     如图4所示,为ADS中在配置选项”ARM Linker“中有一项是”RO Base“,这个地方就表明了运行域中RO的起始地址。"RW Base"为空,这表明在运行域总共RW紧跟着RO的后面。

                                                             

    启动代码做的工作就是如图3中虚线部分所指示的,将各个段搬移到指定的位置,然后将ZI初始化为0.下面这段程序就是围绕这个工作来展开。

    116    copy_proc_beg                              

    117             adr         r0,ResetEntry          

    118             ldr          r2,BaseOfROM        

    119             cmp       r0,r2                         

    120             ldreq      r0,TopOfRom          

    121             beq        InitRam                    

    122             ldr          r3,TopOfRom           

    123    0                                                      

    124             ldmia      r0!,{r4-r7}                  

    125             stmia      r2!,{r4-r7}                  

    126             cmp       r2,r3                          

    127             bcc         %B0                         

    128             sub        r2,r2,r3                      

    129             sub        r0,r0,r2                      

     有了上面的讲解,再看第116~129行程序就很容易理解。在上述程序中,BaseOfROM和TopOfROM是用DCD分配的内存单元,里面存放的是RO的起始地址和结束地址(准确点说是结束地址+1)。该起始地址和结束地址是由编译器自动生成的,在程序中使用即可,见第183~184行

     183          BaseOfROM      DCD        |Image

RO
Base|     

     184          TopOfROM        DCD        |Image

RO
Limit|      

   其|Image

RO
Base|   和|Image
RO
Base|  是ADS编译器自动生成的符号,在该启动代码的开头使用IMPORT引入了,如下

     28            IMPORT    |Image

RO
Base|                 ;Base of ROM code

 

       29           IMPORT    |Image

RO
Limit|                  ;End  of   ROM  code  dd                         

     

表1  编译器生成的符号
编译生成的符号 含义
|Image
RO
Base|
RO段起始地址
|Image
RO
Limit|
RO段结束地址+1
|Image
RW
Base|
RW段起始地址
|Image
RW
Limit|
RW段结束地址+1
|Image
ZI
Base|
ZI段起始地址
|Image
ZI
Limit|
ZI段结束地址+1

    下面接着分析第116~129行。

    第117行,使用adr指令将ResetEntry的相对地址加载到寄存器r0中,这个地址就是0。

    第118行,使用ldr指令将RO的起始地址加载到寄存器r2中。

    第119行,比较r0和r2是否相同,如图5所示,可以看出r0和r2并不相等,因此第120~121行并不执行。

    第122行,使用ldr指令将RO的结束地址+1加载到寄存器r3中。

    执行完上述指令后,图5详细展示程序执行的最后结果,下面的工作就是将代码从r0指示的加载域中的RO搬移到r2、r3所限定的运行域中的RO。

    第123行,定义了一个局部标号0。

    第124行,使用批量加载指令ldmia,将r0地址出的数据加载到寄存器r4~r7中,注意后面的感叹号“!”说明取完数据后,r0的地址自动更新,即指向下一个地址处。此外,可以得出这样的结论每次搬移16个字节(每个寄存器是4个字节的长度,共4个寄存器) 。

    第125行,将r4~r7中的数据存储到r2开始的地址处,同时r2地址自动更新。

    第126~127行,比较r2和r3的值,如果r2的值小于r3的值,就跳转到第123行定义的局部标号0处知心。更形象的理解是,如图5所示,当r2小于r3时,说明RO还没有搬移完,因此就接着搬移,直到r2的值大于r3的值。

    第128~129行,这两行的作用就是调整r0的值,使其指向加载域中RW的起始地址。有的人会说,怎么r0就指向RW起始地址呢?已知咱们的RW段紧跟着RO段,当搬移完RO内容后,很可能r2不一定等于r3,如果r2大于r3,说明r2指向的地址超过了运行域中的RW段起始地址,此时r0指向的地址也超过了加载域中RW段的起始地址。为了更形象的展示这一调整的过程,请看图6:

      由第124行的讨论可知,每次搬移16个字节,因此最后肯那个出现的情况如图6所示,当r2的值大于r3的值时,停止搬移,那么现在的情况是已经完成了RO从加载域到运行域的搬移,下面需要完成RW从加载域到运行域的搬移。面临的首要问题是如何在加载域中找到RW的起始地址,现在知道的信息是在加载域中RW紧邻着RO存放。由图6可以看到r0已经移到了RW,只要计算出这个偏移,即可计算出RW的首地址。这个偏移地址怎么计算呢,其实就是r2-r3。

     第128行,计算出偏移地址r2-r3,将其存放在寄存器r2中。

     第129行,将r0的值减去这个偏移地址,即得到了RW的起始地址。

    其实,在这种加载域和运行域布局情况下,即加载域和运行域中RO和RW紧挨着存放,完全可以将RO和RW整体搬移,但是该启动代码照顾到更为一般的情况,并没有采取这种搬移方法,而是采取各个段分别搬移的方法。下面的程序就是搬移RW段。

    130    InitRam                                      

    131            ldr           r2,BaseOfBSS    

    132            ldr           r3,BaseOfZero    

    133    0                                                

    134           cmp        r2,r3                     

    135           ldrcc        r1,[r0],#4             

    136           strcc       r1,[r2],#4              

    137           bcc         %B0                     

    138           mov        r0,#0                    

     139          ldr          r3,EndOfBSS        

     140    1                                               

     141          cmp       r2,r3                      

     142          strcc       r0,[r2],#4              

      143         bcc        %B1                      

    上述的BaseOfBss和BaseOfZero仍然是用DCD分配的内存单元。如下:

       185        BaseOfBss    DCD    |Image

RW
Base|     ;RW段起始地址

       186        BaseOfZero   DCD    |Image

ZI
Base|        ;ZI段起始地址

       187        EndOfBss      DCD    |Image

ZI
Limit|        ;ZI段结束地址

      有了前面RO搬移的讲解,下面RW的搬移工作变得较为简单。在RO搬移完以后,r0已经指向了加载域中RW的起始地址处。

    第131行,将r2指向运行域中RW的起始地址处,其中BaseOfBSS是在185行定义的。

    第132行,将r3指向RW的结束地址+1处,又因为ZI在RW后面紧挨着存放,所以可以得出这样的结论:RW的结束地址+1就是ZI的起始地址。

    RW的搬移如图7所示:

     第133行定义了一个局部标号 0。

     第134行比较r2和r3的值。

     第135行,ldr指令后面的cc表示条件执行,如果r2的值小于r3的值,就从r0地址处取出一个字数据,加载到r1中,然后r0自动加4,即r0=r0+4

     第136行,将r1中的数据存储到r2指向的地址处,然后,r2的值自动加4,即r2=r2+4。

     第137行,如果r2的值小于r3,则跳转到133行定义的局部标号0处执行。

     经过前面的讲解,已经实现了RO和RW的搬移工作,下面需要做的就是将ZI初始化为0即可。注意,这里并不是ZI的搬移,因为在加载域中没有ZI,所以不存在搬移这一说法。

     138          mov        r0,#0                        

     139          ldr          r3,EndOfBSS           

     140    1                                                   

     141          cmp       r2,r3                          

     142          strcc       r0,[r2],#4                  

     143          bcc        %B1                          

     RW搬移结束后,r2指向了RW的结束地址+1处,即ZI的起始地址处。

     第138行,将0赋值给寄存器r0.

     第139行,将ZI的结束地址+1加载到寄存器r3,即r3指向了ZI的结束地址+1处。程序执行到了现在,加载域中的分布情况如图8所示,可以看出,r2指向了ZI的起始地址,r3指向了ZI的结束地址+1处。

      第140~143行就是将ZI用0填充,即实现了初始化为0。

     到此为止,启动代码做的工作已经接近尾声,下面回顾一下启动代码将程序从加载域到运行域的搬移过程。

    ① 找到加载域中各个段的起始地址和结束地址。

    方法:用adr指令找到了RO的起始地址。

    ② 找到运行域中各个段的起始地址和结束地址。

   方法: 使用编译器自动生成的各个段的起始地址和结束地址。

    ③ 使用循环执行搬移即可。

 

      144         ldr         r0,=HandleIRQ

      145         ldr         r1,=IsrIRQ

      146         str          r1,[r0]

     对于第144~146行,就是安装中断向量表。

       147        b         Main   ;

      第147行使用b跳转指令跳转到C语言函数Main函数处执行。

 

 

    到这里,我有了一个大概的想法,可不可以在MDK中自带的S3C2440.s中加入这段由加载域搬移到运行域,然后用MDK生成bin文件,利用J-Flash ARM将bin文件下载到nor flash中去,这样子代码就可以搬移到SDRAM中去运行了。不能用axf好像,我看网上说是什么axf携带地址信息,现在还不是很懂。

   我想上述方法可行,关键就是要RO  RW怎么安放,这又好像和分散加载文件scatter有关,再接着看。

RealView MDK中如何获得RO,RW,ZI的地址和长度?

    问题分析:

    在RealView MDK里有专门的字符用来表示RO,RW,ZI的起始地址和长度。

    解决办法:

   1.在不使用Scatter文件时,默认的为Image

RW
Base、Image
RW
Limit、Image
RO
Base、Image
RO
Limit、Image
ZI
Base和Image
ZI
Limit等6个地址,它的长度这样计算:Length = (Image
RW
Limit-Image
RW
Base)。

    2. 在使用Scatter文件后,上述的6个默认地址没有了,取而代之的是Image

Base 和Image
Limit表示的地址,长度计算的方法和上述一样,即Length = (Image
Limit-Image$$段名

    3. 关于Scatter文件的使用方法请参考下面的网址:http://www.realview.com.cn/wen-list3.asp?id=330

 

试图搞懂MDK程序下载到flash(二)--分散加载文件scatter

版权声明:本文为博主原创文章,未经博主允许不得转载。 分散加载文件概念      对于分散加载文件的概念,在《ARM体系结构与编程》书第11章有明确介绍。      分散加载...

试图搞懂MDK程序下载到flash(二)--分散加载文件scatter

分散加载文件概念      对于分散加载文件的概念,在《ARM体系结构与编程》书第11章有明确介绍。      分散加载文件(即 scatter file,后缀为 .scf)是一个文本文件,通过编...

试图搞懂MDK程序下载到flash(三)--MDK配置选项Linker、Target、Debug的理解分析

在写这一节时,我还是想再重复一下自己写这文章的目的,我的目的就是为了实现将MDK编写的裸机程序可不可以脱机运行,也就是不用调试的方法,因为调试的话程序默认是在SRAM中运行的,掉电丢失。而要脱机运行,...

试图搞懂MDK程序下载到flash--MDK配置选项Linker、Target、Debug的理解分析

在写这一节时,我还是想再重复一下自己写这文章的目的,我的目的就是为了实现将MDK编写的裸机程序可不可以脱机运行,也就是不用调试的方法,因为调试的话程序默认是在SRAM中运行的,掉电丢失。而要脱机运行,...
  • whxngb
  • whxngb
  • 2015年12月30日 09:51
  • 255

试图搞懂MDK程序下载到Flash(四)--生成bin文件下载到Nor Flash

喜讯啊!!下载到flash中的一种实现了啊!!!鸡冻了!终于可以脱机运行了,尽管是下载到了Nor Flash中运行,还没有下载到Nand Flash中运行,但是这也是一个进步了吧,下面详细介绍一下MD...

试图搞懂MDK程序下载到Flash(四)--生成bin文件下载到Nor Flash

版权声明:本文为博主原创文章,未经博主允许不得转载。      喜讯啊!!下载到flash中的一种实现了啊!!!鸡冻了!终于可以脱机运行了,尽管是下载到了Nor Flash中运行,还没有...

试图搞懂MDK程序下载到flash(五)--使用MDK中的download直接下载到nor flash

也不知道怎么弄的,今天突然可以使用MDK中的Flash/Download将程序下载到Nor Flash了,像开发51一样方便。现在重新整理一下,将使用MDK中的download功能,将裸机程序下载到n...

试图搞懂MDK程序下载到flash(五)--使用MDK中的download直接下载到nor flash

也不知道怎么弄的,今天突然可以使用MDK中的Flash/Download将程序下载到Nor Flash了,像开发51一样方便。现在重新整理一下,将使用MDK中的download功能,将裸机程序下载到n...

对ARM加载域和运行域的理解(ARM程序是怎么运行的)

对ARM加载域和运行域的理解 一般而言,一个程序包括只读的代码段和可读写的数据段。在ARM的集成开发环境中,只读的代码段和常量被称作RO段(ReadOnly);可读写的全局变量和静态变量被称作RW段...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:试图搞懂MDK程序下载到Flash(一)--Nand Flash启动流程,加载域和运行域
举报原因:
原因补充:

(最多只允许输入30个字)