上图是at91sam9g45的SMC标准读时序图,通过寄存器配置,设置图中6个时间参数值,满足norflash的时序要求即可。(3)bootstrap的移植?上面已经完成了代码段1的任务,下面就来实现代码段2,也就是移植好的将要放在norflash
0地址处的代码bootstrap。既然是移植,那就有个参考。当然其实源码是最好的参考,但是从一个菜鸟的角度思考,自己当时一心只想着把手里的9m10g45开发板上的东西全部移植过来,所以就将这块板子作为参照了。那我就有几个问题需要回答,在9m10g45开发板上,bootstrap的功能是什么?它是在什么时候什么样的环境下执行的?bootstrap的功能参考《U-boot在AT91RM9200上的全线移植分析原始版》可知,在9m10g45开发板上,bootstrap放在nandflash的0地址处,该代码主要实现SDRAM的初始化,并把u-boot拷贝到SDRAM的固定地址处,然后跳转到该地址处,开始执行u-boot。可以看到bootstrap的出现主要是考虑到有的CPU片内SRAM空间太小(例如AT91RM9200的只有16K),无法直接装载u-boot镜像,所以需要一个6K左右的bootstrap加载u-boot到SDRAM执行。at91sam9g45片内虽然有64K的SRAM,但是想要装载u-boot还是有点困难。参考Bootstap v1.6源码,main.c很简单,主要就调用了两个函数hw_init()和load_norflash(IMG_ADDRESS,
IMG_SIZE, JUMP_ADDR)。hw_init()函数实现一些硬件的初始化,包括DBGU,时钟,DDRAM。load_XXXflash(IMG_ADDRESS,
IMG_SIZE, JUMP_ADDR)函数即从相应的flash(norflash,nandflash或dataflash)中下载u-boot到SDRAM的JUMP_ADDR地址处,然后bootstrap就算完成任务了,跳转到JUMP_ADDR地址。bootstrap的执行环境从上面上电启动过程一篇知道,在9m10g45开发板上,上电后检测到BMS=H,则内部ROM被映射到0x00000000处,开始执行ROM内固化的第一段代码boot
program。设置堆栈,使能主时钟,初始化C变量,初始化PLLA,然后初始化nandflash,检测到从nandflash启动(如何检测的可以参考datasheet),然后拷贝nandflash里的bootstrap到片内SRAM
0地址处,最后通过SRAM被映射到0x00000000处开始执行bootstrap,CPU的控制权就交给了bootstrap。这样看来,bootstrap开始执行前,已经做了不少工作,如果要上电从片外的norflash启动,CPU是从0状态启动的,就是说还没有执行任何的代码。这样我们要成功把bootstrap移植到norflash,就要在它之前实现boot
program的部分功能,将这段代码放在norflash的0地址处先于bootstrap执行,由它为bootstrap创造一个同样的执行环境。并且,实际上开发板上nandflash里面的bootstrap只是原始数据,bootstrap真正的运行是被拷贝到片内SRAM里面,所以这段代码似乎还要负责将norflash里的bootstrap拷贝到片内SRAM。问题似乎变得稍微复杂了一些?一方面,目前没有固化在ROM片内的那部分代码的源码,它具体做了多少工作还不知道,只是根据参考资料了解到它的主要功能,另一方面作为一个刚入门的菜鸟而言,对自己写代码是没有一点信心的,因为无论是对ARM处理器还是程序的编译连接执行都了解的不多。使能主时钟,把bootstrap拷贝到片内SRAM去执行,起码这两个功能是要实现的。真的要自己去加入这样一段代码么(呵呵,见笑了,其实实现挺简单的,确实自己的水平还不够)?所幸Bootstrap v1.6的源码比较简单(当然啦,因为它生成的镜像大小不能超过十几K),条理也比较清楚ctrl0_gnu.S------------>main.c------------>ctrl0_gnu.S。我们不妨先浏览一下源码(代码的注释可以很好的帮助我们理解功能)。我发现在ctrl0_gnu.S里面,主要是初始化程序堆栈,重定位到SRAM,设置使能主时钟,初始化一些段,跳转到main.c,继而初始化SDRAM,拷贝u-boot到SDRAM,跳转到SDRAM(我只是走马观花的理了一下流程,有些功能怎么实现的为什么需要我还不清楚)。可以看到,在bootstrap主要功能实现(main.c)前执行的ctrl0_gnu.S也做了一些工作,其中就包括使能主时钟,如果上面所说的“重定位到SRAM”能实现把bootstrap拷贝到片内SRAM去执行(为什么会有这种猜测?因为这段代码的注释是在使用norflash的情况下条件编译的,而且代码的注释是“When
running from NOR, we must relocate to SRAM prior to resetting the clocks and SMC timings”,这段代码确实是一段拷贝功能),那么就不需要我们去给bootstrap创造和9m10g45开发板上一样的运行环境,它自己就给自己创造了。我们为什么必须把bootstrap拷贝到SRAM执行?程序是可以直接在norflash上运行的,不考虑执行速度的要求,我们不拷贝直接将源码编译生成的镜像文件下载到norfalsh执行可以不?这里又需要多考虑一点。解答这个问题又学到了不少东西:程序的加载地址,链接地址,运行地址的作用(是的,在这之前我竟然不知道这些东东,尽然不知道链接脚本,之前写好程序直接gcc生成可执行文件就运行了。当然,我现在的理解也很肤浅,以后好好补课。)我们写好的.c程序通过编译汇编(编译器汇编器)生成相应的二进制目标代码,然而这时程序中用到的一些地址(比如函数的跳转地址,标号代表的地址)还只是相对地址,相对于第一条代码的偏移量,我们还需要一个链接器,该链接器通过链接脚本.lds指定的参数得到.c程序数据段,代码段等各部分的链接地址(暂不考虑动态链接),通过链接地址就计算出了程序中代码的绝对地址(相当于链接地址给了一个基地址,将相对偏移量换算成绝对地址),然后链接得到代码各个段(section)的在内存中的分布,我们就可以下载到相应内存中去执行了。举个例子,假设链接脚本里面指定了代码段(.text)的地址是A,然而我们下载的时候加载到了内存地址B处,那么遇到代码中的地址跳转指令要跳到A
offset(这是链接的时候计算出来的),然而实际要执行的代码可能在B
offset处,那么程序就“跑飞”了。那么,是不是加载地址必须等于链接地址?也不是的,例如如果代码里面没有跳转指令或者说没有用到绝对地址(相对跳转),这就是所谓的地址无关代码(无论加载到哪里,都可以执行)。加载地址:程序实际在内存中存储的地址,也就是被下载到内存的地址。一般通过设置下载工具实现。链接地址:链接器规定的程序的运行地址。程序中的绝对地址都是在链接过程中通过该地址计算出来的,一般代码的运行地址(执行时候的地址)应该等于该链接地址,除非地址无关的代码。可能等于加载地址,也可能不等于加载地址。一般在链接脚本中指定。运行地址:程序实际运行时候所处的地址。可能等于加载地址(这时一般加载地址等于链接地址等于运行地址,直接在存储程序的地方flash,rom执行,这样执行速度比较慢),也可能等于链接地址(加载地址可以不同于链接地址,该代码是被运行于它之前的代码从加载地址处拷贝到该地址处的,这是常见的情况,代码存放在flash,rom中,执行在sram,sdram中),也有可能既不等于加载地址也不等于链接地址(除非是地址无关代码——在哪里都能正常执行,否则无法正常执行)。 <说明一下,上面的认识和举的例子可能有许多不当之处,只是为了易于理解,而且我就是这样理解的。但是要细究,还是需要花点时间看看这方面的书籍>我们可以在bootstrap源码相应目录下找到makefile,board---->at91sam9m10g45ek---->nandflash---->Makefile里面:# Link Address and Top_of_MemoryLINK_ADDR=0x300000TOP_OF_MEMORY=0x304000此处即设置了链接地址,对应的正是片内SRAM的首地址。然后在链接的过程,通过指定参数-Ttext来告诉链接器该链接地址:LDFLAGS =-T $(BOOTSTRAP_PATH)/elf32-littlearm.lds -Ttext $(LINK_ADDR)现在,那么我们是不是可以改变这个链接地址为norflash的地址,bootstrap就不用拷贝进SRAM中执行了?是的。到此,我们要为norflash上的bootstrap创造的运行环境已经解决,其实回头一看,没多少东西,但是我却感觉学到不少知识。我们还有一个问题没有解决,ctrl0_gnu.S中重定位到SRAM这段代码是什么功能?那么我们来分析一下这段代码。 接下来的代码就实现了将_stext到_edata部分的代码拷贝到LINK_ADDR指向的地址处,然后继续执行_setup_clocks段代码。怎么实现的?关键是这一句:ldrcc r2,
[r1], #4。寄存器间接寻址。把r1寄存器的内容(=0)作为地址,该地址处的连续4字节拷贝到寄存器r2中。r1是0,该0地址是相对地址,相对于程序的运行地址的,因为存储在norflash中,运行前也没有被拷贝到别处,所以就是当前PC值。所以这段代码就实现了自拷贝的功能,就是说如果定义了norflash,bootstrap会将自己拷贝到SRAM(地址0x300000)中去。你可能已经发现了,这段代码的运行地址不等于链接器指定的链接地址,对,ctrl0_gnu.S里这段代码之前的部分都是地址无关的。拷贝完成后,跳转到_setup_clocks标号处的代码段,而标号在链接前都是个偏移量,经过链接得到绝对地址,所以这个跳转已经跳转到了LINK_ADDR
offset去运行了。这一段理解很重要,也很艰难,不过理解之后的喜悦也是难以言表的。之后会发现像u-boot,kernel等好多的源码中汇编部分都有自拷贝功能代码。(为什么?自拷贝也是一种实现代码运行地址无关的方法呀,不管代码加载到在哪里,我都将自己拷贝到链接地址去执行。)