AT91SAM9G45上电启动和bootstrap移植过程

2019-04-14 21:14发布

出处:http://blog.sina.com.cn/s/blog_686ced250102vyml.html
猛一回头,觉得笔记不能断掉,所以趁空好好整理一下思路。 最近做了什么?从9月底到今天约20天三周的时间里,基本就是:熟悉canopen协议栈,熟悉bootstrap,uboot源码及其移植,期间了解了下ARM裸机启动和内部时钟配置,norflash驱动移植。 从哪里说起呢?当然是ARM裸机启动。我觉得这也应该是做嵌入式开发的起点,从裸机启动,程序下载,bootstrapubootosfs、驱动的移植,到最后是上层应用程序的开发。所以很高兴,我认识到了学习的路线,并且我的工作就是要走遍全程。  (一)AT91SAM9G45上电启动过程 主要参考《U-bootAT91RM9200上的全线移植分析原始版》第31AT91RM9200的启动方式,《SAM926X 裸奔指南》,《at91sam9g45 datasheet》第11boot strategies。参考资料都很详细,我这里画一幅图总结一下。
上面的图示已经说的很清楚了,一般裸机第一次上电启动是通过SAM-BA或者DBGU往存储器下载正确的程序,之后可以从其它一些存储器启动,比如片外norflashnandflash,dataflash等等。我手里有块at91sam9m10g45-ek的开发板,就是从EBI0 CS3上的nandflash启动的,nandflash的空间分布如上图的NVM空间分布,而我的目前项目是要从片外的norflash启动的。就上图我再提几个问题,在后文中都会一一解答: 1.如果BMS=低电平,CPU上电能否直接成功从norflash读取第一条指令?这里norflash的型号有没有限制? 2. Bootstrap这一步是不是必须的?什么情况下可以省略?   3. bootstrapu-boot通过SAM-BA或者DBGU直接下载至片内的SRAM或者SDRAM,能正常执行不? (二)AT91SAM9G45norflash启动: 主要是解决如下几个问题 (1) at91sam9g45上电能不能正确的读我们的norflash执行第一条指令?这个norflash的型号支持么? 这是个关键的问题,如果答案是否定的,那么只能改板,选择上电BMS=1nandflash等启动。从atmel的技术人员那边得到的答案是肯定的;从datasheet上看,检测到BMS=0,片内慢速RC做时钟源,连接在EBI CS0上得norflash被映射到地址0x00000000,开始执行第一条指令,并没有提及norflash的型号,推断是型号无关的;从网络上的资料,解释说norflash芯片都是使用的是统一的接口,读取不需要驱动,而nandflash不同,需要相应驱动才能读写。所以,我们暂且认为从norflash启动是没有问题的。 (2) norflash里面的程序通过什么下载进去? 现在的项目实际情况是,SAM-BA只支持对现有的片内SRAMDDRAM进行烧写,J-FLASH也是无法检测到使用型号的norflash,手动选择了之后又会提示ID校验错误,无法读写。但是J-LINK连接正常。所以,准备通过SAM-BA烧写代码段1DDRAM的地址1处,烧写代码段2DDRAM得地址段2处。其中代码段1实现norflash的烧写,把代码段2拷贝到norflash地址0处,代码段2即为移植好的bootstrap。然后,我们通过J-LINK的命令设置PC跳转到代码段1处执行。在这个过程中有学习到一些内容。 的内部时钟>    主要参考datasheet 24 时钟源25 电源管理控制器内容 也是用图总结一下:
上图左侧为原始时钟源3个,右侧为由此产生的4种不同时钟,这4种时钟成为后面产生其他各种系统时钟的时钟源。旁边标注的数字为常见应用的典型值。





上图即为系统中各种时钟的产生过程,具体到如何使产生相应频率的时钟,就是配置相应的寄存器,可以参考PMC最后一节。 的读写> 对存储器的操作,最主要的就是实现读,擦除,写这函数,参考已有的S29GL512S10TF101  norflash的驱动,移植过程主要就是配置SMC相应的寄存器,使满足norflash的读写时序,主要参考芯片的datasheet。以读时序为例:
上图是norflash的读时序图;

上图是at91sam9g45SMC标准读时序图,通过寄存器配置,设置图中6个时间参数值,满足norflash的时序要求即可。 3bootstrap的移植? 上面已经完成了代码段1的任务,下面就来实现代码段2,也就是移植好的将要放在norflash 0地址处的代码bootstrap 既然是移植,那就有个参考。当然其实源码是最好的参考,但是从一个菜鸟的角度思考,自己当时一心只想着把手里的9m10g45开发板上的东西全部移植过来,所以就将这块板子作为参照了。那我就有几个问题需要回答,在9m10g45开发板上,bootstrap的功能是什么?它是在什么时候什么样的环境下执行的? bootstrap的功能 参考《U-bootAT91RM9200上的全线移植分析原始版》可知,在9m10g45开发板上,bootstrap放在nandflash0地址处,该代码主要实现SDRAM的初始化,并把u-boot拷贝到SDRAM的固定地址处,然后跳转到该地址处,开始执行u-boot。可以看到bootstrap的出现主要是考虑到有的CPU片内SRAM空间太小(例如AT91RM9200的只有16K),无法直接装载u-boot镜像,所以需要一个6K左右的bootstrap加载u-bootSDRAM执行。at91sam9g45片内虽然有64KSRAM,但是想要装载u-boot还是有点困难。 参考Bootstap v1.6源码,main.c很简单,主要就调用了两个函数hw_init()load_norflash(IMG_ADDRESS, IMG_SIZE, JUMP_ADDR)hw_init()函数实现一些硬件的初始化,包括DBGU,时钟,DDRAMload_XXXflash(IMG_ADDRESS, IMG_SIZE, JUMP_ADDR)函数即从相应的flash(norflash,nandflashdataflash)中下载u-bootSDRAMJUMP_ADDR地址处,然后bootstrap就算完成任务了,跳转到JUMP_ADDR地址。 bootstrap的执行环境 从上面上电启动过程一篇知道,在9m10g45开发板上,上电后检测到BMS=H,则内部ROM被映射到0x00000000处,开始执行ROM内固化的第一段代码boot program。设置堆栈,使能主时钟,初始化C变量,初始化PLLA,然后初始化nandflash,检测到从nandflash启动(如何检测的可以参考datasheet),然后拷贝nandflash里的bootstrap到片内SRAM 0地址处,最后通过SRAM被映射到0x00000000处开始执行bootstrapCPU的控制权就交给了bootstrap 这样看来,bootstrap开始执行前,已经做了不少工作,如果要上电从片外的norflash启动,CPU是从0状态启动的,就是说还没有执行任何的代码。这样我们要成功把bootstrap移植到norflash,就要在它之前实现boot program的部分功能,将这段代码放在norflash0地址处先于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-bootSDRAM,跳转到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源码相应目录下找到makefileboard---->at91sam9m10g45ek---->nandflash---->Makefile里面: # Link Address and Top_of_Memory LINK_ADDR=0x300000 TOP_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 r10,该0地址是相对地址,相对于程序的运行地址的,因为存储在norflash中,运行前也没有被拷贝到别处,所以就是当前PC值。所以这段代码就实现了自拷贝的功能,就是说如果定义了norflashbootstrap会将自己拷贝到SRAM(地址0x300000)中去。你可能已经发现了,这段代码的运行地址不等于链接器指定的链接地址,对,ctrl0_gnu.S里这段代码之前的部分都是地址无关的。拷贝完成后,跳转到_setup_clocks标号处的代码段,而标号在链接前都是个偏移量,经过链接得到绝对地址,所以这个跳转已经跳转到了LINK_ADDR offset去运行了。这一段理解很重要,也很艰难,不过理解之后的喜悦也是难以言表的。之后会发现像u-bootkernel等好多的源码中汇编部分都有自拷贝功能代码。(为什么?自拷贝也是一种实现代码运行地址无关的方法呀,不管代码加载到在哪里,我都将自己拷贝到链接地址去执行。)


bootstrap的移植过程 有了上面理解的基础,移植过程变得简单。依程序为主线,主要修改board---->at91sam9m10g45ek----> at91sam9m10g45ek.c(需要实现void norflash_hw_init(void)函数,参考at91cap9adk知道就是上篇说到的配置SMC的相关寄存器值满足norflash的读写时序),board---->at91sam9m10g45ek----> norflash----> at91sam9m10g45ek.hboard---->at91sam9m10g45ek----> norflash----> Makefile。主要是修改一些参数值,只要清楚了他们代表的意思,就很容易了。 最后编译后生成的镜像下载到norflash0地址处,上电片外引导,成功启动,调试串口输出打印信息。 (最后提一下,移植过程中,SDRAM的初始化部分没有考虑,因为开发板和项目所使用的SDRAM型号一样,且连接方式相同。不过,完整的移植是要考虑这部分的,主要也是实现at91sam9m10g45ek.c中的void ddramc_hw_init(void)函数,配置相关寄存器满足SDRAM时序)




启动的方式

对于S3C2440而言,启动的方式有两种,一是Nor Flash方式启动,二是Nand Flash方式启动。

使用Nor Flash方式启动

Nor Flash的地址范围如下    0x0000.0000—0x0800.0000 (2M Nor Flash)    片内的BootSRAM地址被置为    0x4000.0000—0x4000.DFFF (4K BootSRAM) 由于可以在Nor Flash直接运行代码,因此BootSRAM被映射到别的地址上去,可作为其他用途。 程序映像直接存放到NOR FLASH里面,中断向量表存放在0x0000.0000开始的8×4大小的空间中。 中断产生时,PC被置为相对应的向量地址。如上电或者按Reset键时,PC直接置为0x00,从NOR FLASH的0x00处开始执行。

使用NAND Flash方式启动

此时,片内的BootSRAM地址被置为    0x0000.0000—0x0800.0000 (4K BootSRAM)    NAND Flash地址接NFCE 由于NAND Flash中不能运行代码,因此必须复制到内存之中再运行。       程序映像存放在NAND FLASH中,中断向量表位置在程序映像的最前面。由于NAND FLASH不能运行代码,系统上电或者Reset的时候,内置的NAND FLASH 将访问控制接口,并将中断向量表和引导代码自动加载到内部SRAM(此时该SRAM 定位于起始地址空间0x00000000,容量为4KB),并且置PC值为0x00运行程序(这一切是有芯片内部的硬件逻辑完成的)。之后,SRAM 中的引导程序将操作系统镜像加载到SDRAM 中,操作系统就能够在SDRAM 中运行。启动完毕,4KB 的启动SRAM 就可以用于其他用途。

中断向量表的设置

ARM中的异常与中断总共有7种   按响应优先级从高到低 按中断向量表顺序 复位 复位 数据中止 未定义指令中断 FIQ SWI IRQ 预取指令中止 预取指令中止 数据中止异常 未定义指令、SWI IRQ   FIQ       ARM要求中断向量表必须放置在从0地址开始,连续8×4字节的空间内(ARM720T和ARM9、ARM10也支持从0xFFFF0000开始的高地址向量表),各异常和中断向量在向量表中的位置如下   地址 中断 0x00 Reset 0x04 Undef 0x08 SWI 0x0C Prefetch Abort 0x10 Data Abort 0x14 (Reserved) 0x18 IRQ 0x2C FIQ       当中断产生时ARM处理器强制把PC指针置为中断向量表中相对应的向量地址。因为每个中断向量在向量表中只有一个字节的存储空间,只能存放一条指令,所以通常存放跳转指令,使程序跳转到存储器的其他地方,再执行中断处理。   中断向量表的实现程序通常如下 AREA Boot,CODE,READONLY ENTRY B Reset_Handler ; Reset_Handler is a label B Undef_Handler B SWI_Handler B PreAbort_Handler B DataAbort_Handler B ;for reserved interrupt, stop here B IRQ_Handler B FIQ_Handler       其中的关键字ENTRY是指定编译器保留这段代码,因为编译器可能会认为这段代码是冗余代码,将其优化。链接的时候要确保这段代码被连接到0地址处,并且 作为整个程序的入口点(ENTRY并非总是用来设置程序的入口点的,所以通常需要在链接选项里面显式的设置程序入口点)。