2410 bootloader分析(一)

很早就想写一篇关于2410 bootloader的文章了,以前写过一个lpc系列的启动代码分析,收到了一些网友的反馈.但是因为工作之后写上层应用较多,一直没抽出时间来2410的bootloader.最近刚刚做完一个项目,赶快在下个任务来之前研究一下这个东西.

 

 这个bootloader是优龙的FS2410, 具体配置可上网查相关资料. 由于代码比较长,源程序只贴出重要的.  

 

 

另外要多谢csdn论坛上的yashi前辈,它的解答让我茅塞顿开. 我根据它的解答和自己的理解补充下面关于bootloader解释.

 

2410或2440本身的硬件特性决定了它的启动方式.它里面有个4K 的steppingstone,我们可把叫stepldr或nboot,它的用作第一次加载, 加载另一个大过4K的启动程序,从而完成加载整个操作系统的工作.在wince里,第二次加载的这个启动程序就叫eboot了.而优龙的这块FS2410的板子所带的这个所谓的bootloader,它实际上具有nboot+eboot的功能.所以, 这块板子的BSP里的eboot可以不用编译, 直接编译系统内核就可以了.

 

个人觉得优龙的这种用ADS单独开发bootloader方式不是很好.先不说还要单独安装ADS, 至少bootloader本身就和OAL,驱动程序构成整个BSP, 它的开发应该属于BSP开发的一部分,用PB或vs2005来开发应该比较规范. 

 

好,进入正题.

 

SetClockDivider(1, 1);
SetSysClk(FCLK_200M); 
Port_Init();

分别设置时钟和初始化相关端口.

 

2410有四种类型的时钟,分别是FCLK,HCLK,PCLK和UCLK. FCLK的CPU的时钟,HCLK是内部高速总线的时钟, PCLK是外设的时钟,而UCLK是专门给USB时钟源.这些时钟都是由外部时钟源通过芯片内部PLL(锁相环)而产生的.优龙板子外部时钟源是一个12M的晶振SetClockDivider就是设置除UCLK之外,其它时钟的比例关系.结果是FCLK:HCLK:PCLK =  4:2:1.

 

Port_Init初始化用的IO口.比如GPA全部设置为第二功能, CLE,ALE等引脚被nandflash用到,GPC 和GPD 用来控制LCD. 其实LCD在bootloader可以不要的,因为它对系统本身没用的,只是很多公司用它来在操作系统启动前,显示公司logo等信息 . 注意使端口的上拉无效.

其它端口的配置原理可以参考2410的datasheet.

 

接着到Isr_Init

该函数做中断初始化. 进入该函数,看到下面几行.

pISR_UNDEF = (U32)HaltUndef;
pISR_SWI   = (U32)HaltSwi;
pISR_PABORT = (U32)HaltPabort;
pISR_DABORT = (U32)HaltDabort;
pISR_UNDEF 定义如下:
#define pISR_UNDEF     (*(unsigned *)(_ISR_STARTADDRESS+0x4))(在2410addr.h里定义了整个中断向量的存放位置, 注意存放位置与优先级无关)
#define _ISR_STARTADDRESS                   (SDRAM_END-0x100)
#define     SDRAM_END                               0x34000000


ARM有七种中断(或叫异常),这七种中断按照在向量表的顺序(从0x00到0x1c).每四字节放一个中断, 分别是复位中断,未定义中断, 软中断,预取中断,数据中断,外部中断IRQ和快速中断FIQ.

所以,bootloader把从00x3400000-0x100开始的256字节的空间,依次放入了上述七种中断,以及2410其它外设的中断源.这里可能有个会有疑问, 中断向量有不是应该从0x00处开始的吗,这里怎么不是呢. 其实,ARM有一级中断和二级中断的概念.一线中断用汇编实现,在启动代码里,这个地址必须是从0x0开始,按顺序放置. 比如IRQ中断就必须放在pan lang="EN-US">0x0018处.二级中断一般用C实现,在应用的main里初始化.二级中断的位置可以自己指定,但要于起动代码里对应. 至于为什么是256字节的空间,是因为2410有56个中断源,加上7个异常,一个保留位,一共是64个, 64*4字节就是256.二级中断与一级中断是如何衔接上的呢,简单来说就是跳转了. 具体分析太占篇幅了,网上有人分析过去, 这里给个链接,是我个人觉得分析的比较好的. http://www.myhcs08.com/blog/info/article_detail.aspx?id=190

 

 

回到main函数.

Uart_Init(0, 115200);是串口初始化,这个比较简单,不说了.

MMU_Init()

这一行是MMU初始化.这个要好好分析一下了.

像linux和wince这类的高级操作系统是要硬件的MMU支持的,因为这类操作系统用的是虚拟地址,而硬件操作的是物理地址,一定要一个东西来实现虑拟地址和物理地址的转换,这就是MMU.下面就一步步来分析这个函数.进去MMU_Init,看到下面两行.

MMU_DisableDCache();
MMU_DisableICache();

这两行禁止数据cache和指令cache,直接操作协处理器,比较简单,注意这两个函数要用汇编实现,因为要用到协处理器操作指令.

之所以要禁止上述两个cache是因为下面的设置要求先禁它们,设置完毕再打开.

 

继续往下看,

for(i=0;i<64;i++)
{ 
   for(j=0;j<8;j++)
    {
        MMU_CleanInvalidateDCacheIndex((i<<26)|(j<<5));
    }

}

MMU_InvalidateICache();

 

看一下MMU_CleanInvalidateDCacheIndex的实现,如下:

mcr p15,0,r0,c7,c14,2

MOV_PC_LR

第一行的意思是, 把r0里的值传给协处理器p15的c7寄存器中,第一个操作数是0,第二个操作数是2. r0里存放的是传进来的参数即(i<<26|j<<5).而c7是协处理器中用来操作cache的寄存器,而且是个只写的寄存器.与c14和操作数2组合,它的意思是清理并使数据cache中的某行无效. i和j就唯一指明了要清理的位置.

 

那么原理是什么呢?

 

Cache中的数据以行为基本单位,针对2410, 这一行是32个字节,所以一个虚拟地址的bit[31:5]就可以定位到某一行了.这样的话,i变量有什么用呢.是这样的,2410一共有512个cache行,然后把每8个cache行归为一个cache组,所以有 64个cache组.现在你明白了for(i=0;i<64;i++)和for(j=0;j<8;j++)的意义了吧.至于26和5的含义,就和c7寄存器的位设置有关了.可参见<armarchitecture reference>文档.

 

MMU_InvalidateICache让整个指令cache无效,原理相似,不作分析.

 

 

MMU_DisableMMU();
MMU_InvalidateTLB();

 

 

 

上面两行分别使MMU和TLB无效,设置完地址映射参数后再把MMU打开.下面是设置虚拟地址和物理地址的映射, 这个操作是通过函数MMU_SetMTT来实现的.该函数的实现如下:

void MMU_SetMTT(int vaddrStart,intvaddrEnd,int paddrStart,int attr)
{

   U32 *pTT;
   int i,nSec;
   pTT=(U32 *)_MMUTT_STARTADDRESS+(vaddrStart>>20);

   nSec=(vaddrEnd>>20)-(vaddrStart>>20);

   for(i=0;i<=nSec;i++)
    *pTT++=attr|(((paddrStart>>20)+i)<<20);

}

我下面假设你对一级页表的映射关系很清楚了,如果不清楚, 可以去看<arm architecture reference>. 下面就来深入浅出一下MMU_SetMTT的实现原理. 首先你知道arm是32位芯片,内存地址4字节为单位.如果采用一级页表,如何去找到一个地址呢?

 

第一步,你要知道页表的位置, 这就是所谓的页表址的概念.该值由芯片自动从协处理器的c2寄存器中取出配置.

 

第二步,你要知道页表里的段的位置, 这是段基址的概念.或者叫表内序号. 这个值是虑拟地址的bit[31:20].它和上一步的页表基址构成一级页表的地址项. 所以你应该明白了下面这一行的意思了吧.

 

pTT=(U32*)_MMUTT_STARTADDRESS+(vaddrStart>>20);

 

 

 

_MMUTT_STARTADDRESS就是所谓的页表基址.由第一步可知,该值应该存到c2寄存器里,哪里存呢.接着往下看,会发现MMU_SetTTBase(_MMUTT_STARTADDRESS)这个函数,就是这个函数完成了该操作,由于要对c2寄存器操作,该函数要用汇编实现.这里不分析了.

接着分析MMU_SetMTT.

 

第三步. 知道了页表里段的位置,下面只要知道每个地址在段内的偏移量,不就知道该地址了吗.这个偏移量就是虚拟地址的bit[19:0].你可能要问,怎么这里没有看到对bit[19:0]的操作呢,因为MMU_SetMTT是实现虚拟地址到物理地址的映射,而这个映射是以段为单位的.所以,只要用到bit[20]之后的位数.

 

那么addr是做什么的呢.顾名思义,属性. 它定义了bit[11:0]的值,该值在一线页表表项里不同的位代表不同的函义,比如bit[8:5]表示本段所在的域等等.所以下面这一行

 

*pTT++=attr|(((paddrStart>>20)+i)<<20)

产生了物理地址的高12位,并设置了一组页表的表项.

 

 

接着回到main函数.下面几行:

EnableModuleClock(CLOCK_ALL);   //使能一些用到的外设的CLOCK.
Delay( 0 ) ;
putch('/n');
rMISCCR &= ~(0x3007);           //USB port0,1 = normal, D0~D31pull-up enable
Set_Tout1_Pwm( 60 );//渐渐点亮lcd.
GPB1_TO_OUT();
GPB1_TO_1();//输出1背光led灯一直高亮.
Beep(2000, 1000) ;


这几行操作比较简单,直接看注释.

下面是lcd的初始化,由于2410本身带有lcd controller,所以初始化的操作主要是对相关寄存器设置. 至于原理有兴趣的可以参看下面这篇文章.

http://blog.csdn.net/pony_maggie/archive/2010/01/29/5270147.aspx

(未完待续)

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值