基于OK6410开发板Uboot源码简单分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014754841/article/details/79846490

2018-04-07

OK6410开发板是基于三星S3C6410芯片设计的一款开发板,资源比较丰富,可是想要使用这些资源就需要编写相应的启动引导程序,即BootLoader。当然,想要自己凭空写出BootLoader那简直就是天方夜谭,所以我们需要参考行业中现有的BootLoader,在其基础上再结合实际的控制芯片和开发板相关硬件资源编写适合自己的BootLoader代码。如今市面上常用的嵌入式BootLoader有如下几种: U-Boot, Blob, VIVI,RedBoot 和 ARMboot 等。下面我们就简单分析一下BootLoader行业的龙头老大,U-boot的与S3C6410芯片相关的代码。

首先我们需要找到S3C6410芯片相关的Uboot代码在整个工程中的位置,我们先将下载的源代码通过linux解压后,按如下路径(board\samsung\smdk6410)找到链接文件u-boot.lds,打开链接文件,如下图:

连接文本相关资料请大家自行百度,从代码段发现,S3C6410的uboot是先从cpu/s3c64xx/start.S文件开始的,其中ENTRY(_start)说明入口是从文件start.S的.globl _start处开始的,所以我们需要找到文件start.S的.globl _start处。使用Source Insight打开所有的uboot工程,具体方法自行百度。打开后如下图:

在右边搜索栏中输入start.S,然后找到相对应的路径,如上图。这就是S3C6410的uboot的入口位置,下面看一下uboot都干了啥,能够引导S3C6410启动过程。

入口处代码如下:

这是在配置异常中断向量表,S3C6410共有7种异常中断,分别是复位、未定义指令、软件中断(SWI、预取终止、数据终止、IRQ、FIQ。_not_used我也不知干啥的,可能是为了兼容性吧。

从上面代码我们发现第一步是跳转到reset位置,我们找到该位置。如下图:

从注释我们知道这是在将cpu设置成Supervisor (svc)模式。但是它是如何实现的呢?步骤如下:

1. 使用程序状态寄存器操作指令mrs程序状态寄存器cpsr内容复制到通用寄存器r0中;

2.使用位清除指令bic将通用寄存器r0中的低5位清除,其余为保持不变(0x1f= 2b0001 1111);

3. 使用逻辑或指令orr将通用寄存器r0的第0、1、4、6、7位置1,其余位保持不变(0xd3 =  1101 0011)

4. 使用程序状态寄存器操作指令msr将通用寄存器r0内容写入程序状态寄存器cpsr中。

此时,程序状态寄存器cpsr中值就是0xd3,对应二进制为2b1101 0011。根据ARM架构参考指导手册A2.5节介绍,第6位代表FIQ状态位、第7位代表IRQ状态位,cpsr的最低五位代表着cpu运行模式,如下图:




所以当前cpu状态就为svc模式、关闭FIQ和IRQ。

我们继续往下面看,如下图:


从注释我们知道接下来是要做的就是在重启系统时初始化重要的寄存器的操作,首先就是关闭I/D(指令/数据) 高速缓存器。步骤如下:

1. 将通用寄存器r0赋值为0 ;

2.协处理器p15把r0设置为0并且赋值给第7个寄存器c7;

3. 协处理器p15把r0设置为0并且赋值给第8个寄存器c7。

上面三步主要就是配置访问caches和TLB的权限,详请参考Arm1176jzfs内核说明System Control Coprocessor章节,部分说明如下:


mcr   p15, 0, r0, c7, c7, 0指令关闭cache,通过查找Arm1176jzfs内核说明3.2.1 Register allocation章节,如下图红框所示:


跳转到3-71查看该条指令详细信息。

mcr   p15, 0, r0, c8, c7, 0 关闭TLB,如上,查找指令表如下图:


跳转到page 3-86查看该条指令详请。

通过上面分析可知,操作协处理器很简单,就是查找Arm1176jzfs内核说明3.2.1 Register allocation章节中列出的指令表,然后根据实际情况编写命令。都是“王八的屁股-规定”的东西。

接着往下看,如下图:


关闭MMU和caches。步骤如下:

1.  协处理器p15把第一个寄存器c0经过操作的值赋值给通用寄存器r0;

2.  使用位清除指令bic将通用寄存器r0中的第8、9、13位清除,其余位保持不变(0x00002300 = 2b0000 0000 00000000 0010 0011 0000 0000);

3.  使用位清除指令bic将通用寄存器r0中的第0、1、2、7位清除,其余位保持不变(0x00002300= 2b0000 0000 0000 0000 0000 0000 1000 0111);

4.  使用逻辑或指令orr将通用寄存器r0的第1位置1,其余位保持不变(0x 0x00000002 = 2b0000 0000 00000000 0000 0000 0000 0010);

5.  使用逻辑或指令orr将通用寄存器r0的第12位置1,其余位保持不变(0x 0x00001000 = 2b0000 0000 00000000 0001 0000 0000 0000);

6.  协处理器p15把r0的值赋值给第1个寄存器的c0(控制寄存器)。

mrc   p15, 0, r0, c1, c0, 0

mcr   p15, 0, r0, c1, c0, 0查找指令表如下图:

跳转到page 3-44查看该条指令详请。控制寄存器bit位说明如下:


查看Operation of the ControlRegister并且对照上面的操作,第0位和12位为0 ,所以关闭了MMU和caches,如下图:



当然,同时也开启了字节对齐检测功能和流量预报功能,不过流量预报功能不止干啥的。有兴趣的小伙伴可以深入研究一下,研究好了希望可以共享出来。

接着往下看,如下图:


配置外设端口地址重映射,步骤如下:

1. 通过伪指令ldr将内存地址0x70000000赋值给通用寄存器r0,注意:是内存地址不是数值

2. 使用逻辑或指令orr将通用寄存器r0的第0、1、4位置1,其余位保持不变(0x13 = 2b00010011),即r0中的值为0x70000013;

3. 协处理器p15把r0的值赋值给第15个寄存器的c2(Write Peripheral Port Memory Remap Register)

mcr   p15,0,r0,c15,c2,4查找指令表如下图:


跳转到page 3-130查看该条指令详请。外设端口地址重映射寄存器bit位说明如下:


查看Peripheral Port Memory Remap Registerbit functions并且对照上面的操作,可知基地址为0x70000000,连续256m内存,如下图:


即0x70000000~0x7fffffff全部用于外设地址,参见S3C6410用户手册MEMORY MAP章节。

2018-04-08  接上

接着往下看,我们发现有一段关于ONENAND的预编译,这里不管他,因为我们采用的是NAND Flash启动。跳过这段代码,找到如下代码:


跳转至lowlevel_init,用于配置PLL,Mux,memory等特殊位,我们进入lowlevel_init,看其是如何实现的,到底干了啥?lowlevel_init在lowlevel_init.S文件中。如下图:


首先将lr寄存器的值保存到通用寄存器r12中,用于以后返回跳转位置使用。lr(r14)一般来说有两个作用:
1.当使用bl或者blx跳转到子过程的时候,r14保存了返回地址,可以在调用过程结尾恢复。
2.异常中断发生时,这个异常模式特定的物理R14被设置成该异常模式将要返回的地址。

接着往下看,代码如下图:


这段代码主要用来设定GPK、GPL、GPF的类型及数据等。实现步骤如下:

第1步,使用伪指令将ELFIN_GPIO_BASE地址赋给通用寄存器r0,其中ELFIN_GPIO_BASE在头文件s3c6410.h中定义,如下:


第2步,使用伪指令将地址0x55555555赋给通用寄存器r1;

第3步,使用str指令将r1存放的值赋给ELFIN_GPIO_BASE+GPKCON0_OFFSET地址处;GPKCON0_OFFSET定义如下:即将地址0x7f008800设置为0x55555555;

第4步,执行第2步,使用str指令将r1存放的值赋给ELFIN_GPIO_BASE+GPKCON1_OFFSET地址处;GPKCON0_OFFSET定义如下:即将地址0x7f008804设置为0x55555555;

通过查看s3c6410用户手册GPIO章节,可知地址0x7f008800对应着GPKCON0寄存器,0x7f008804对应着GPKCON1寄存器,如下图:


将这两个寄存器设置为0x55555555,也就是将Port K配置为DATA_CF功能,如下图:


第5步,使用伪指令将地址0x22222666赋给通用寄存器r1;

第6步,使用str指令将r1存放的值赋给ELFIN_GPIO_BASE+GPLCON0_OFFSET地址处;GPLCON0_OFFSET定义如下:即将地址0x7f008810设置为0x22222666;

地址0x7f008810对应着GPLCON0寄存器,如下图:


将这个寄存器设置为0x22222666,也就是将Port L配置为ADDR_CF功能,如下图:


第7步,使用伪指令将地址0x04000000赋给通用寄存器r1;

第8步,使用str指令将r1存放的值赋给ELFIN_GPIO_BASE+GPFCON_OFFSET地址处;GPFCON_OFFSET定义如下:,即将地址0x7f0080A0)设置为0x04000000;

第9步,使用伪指令将地址0x2000赋给通用寄存器r1;

第10步,使用str指令将r1存放的值赋给ELFIN_GPIO_BASE+GPFDAT_OFFSET地址处;GPFCON_OFFSET定义如下:,即将地址0x7f0080A4)设置为0x2000;

地址0x7f0080A0对应着GPFCON寄存器,地址0x7f0080A4对应着GPFCON寄存器,如下图:


将GPFCON寄存器设置为0x04000000,也就是将PF13配置为输出功能,如下图:


将GPFDAT寄存器设置为0x2000,也就是将PF13配置高电平,如下图:


至于为什么要这么设置,为什么要设置这几个寄存器,我想应该是和smdk6410板卡硬件使用资源有关吧。

接着往下看,代码如下:


用于点亮led小灯,步骤如下:

第1步,使用伪指令将ELFIN_GPIO_BASE地址赋给通用寄存器r0,其中ELFIN_GPIO_BASE的定义参考上面;

第2步,使用伪指令将地址0x00111111赋给通用寄存器r1;

第3步,使用str指令将r1存放的值赋给ELFIN_GPIO_BASE+GPMCON_OFFSET地址处;GPKCON0_OFFSET定义如下:即将地址0x7f008820设置为0x00111111;

第4步,使用伪指令将地址0x00000555赋给通用寄存器r1;

第5步,使用str指令将r1存放的值赋给ELFIN_GPIO_BASE+GPMPUD_OFFSET地址处;GPMPUD_OFFSET定义如下:即将地址0x7f008828设置为0x00000555;

第6步,使用伪指令将地址0x002a赋给通用寄存器r1;

第7步,使用str指令将r1存放的值赋给ELFIN_GPIO_BASE+GPMDAT_OFFSET地址处;GPMDAT_OFFSET定义如下:,即将地址0x7f008824设置为0x002a;

0x7f008820对应着GPMCON寄存器,0x7f008824对应着GPMDAT寄存器,0x7f008828对应着GPMPUD寄存器,如下图:


将GPMCON寄存器设置为0x00111111,也就是将PM0~5配置为输出功能,如下图:


将GPMDAT寄存器设置为0x002a,也就是将PM1、PM3、PM5配置高电平,如下图:


将GPMPUD寄存器设置为0x00000555,也就是将PM0~5配置为下拉模式,如下图:


我想这里的设置应该也是和smdk6410板卡硬件使用资源有关吧。


2018-04-09

接上文:

接着往下看,代码如下:


呵呵,完全不知干啥的,暂时跳过,等明白了再来完善,请知道的能够分享一下。

接着往下看,如下:


关闭看门狗,如何实现的呢?

第1步,使用伪指令将地址0x7e000000赋给通用寄存器r0;

第2步,使用逻辑或指令将r0的数值改为0x7e004000,该地址对应的就是关门狗配置寄存器,如下:


第3步,将0赋给通用寄存器r1,;

第4步,使用str指令将r1存放的值赋给r0所存的地址处,即关门狗配置寄存器,及关闭看门狗,如下图:


接着往下看,代码如下:


清除外部中断位,实现步骤如下:

第1步,使用伪指令将地址ELFIN_GPIO_BASE+EINTPEND_OFFSET赋给通用寄存器r0,ELFIN_GPIO_BASE定义如下:,EINTPEND_OFFSET定义如下:,即将0x7f008924存放在r0中;

第2步,使用ldr指令(这里与第一步同名不同功能),将R0中的数所指定的地址的内容传输到r1中;

第3步,使用str指令将r1存放的值赋给r0所存的地址处,为毛需要这样,因为读一次外部中断引脚,读完之后中断信号就都被清除,上面已经禁止了中断,所以读完之后就不会再产生中断了。

地址0x7f008924对应的是EINT0PEND,如下图:


继续往下读代码,如下:

这段代码主要就是禁止所有中断,清除中断标志位,实现步骤如下:

第1步,使用伪指令ldr分别将中断控制器基地址存放到r0、r1中,定义如下;

第2步,将0x0取反后赋给通用寄存器r3,即r3 = 0xffff;

第3步,分别使用str指令r3存放的值赋给r0所存的地址+ oINTMSK处与r10所存的地址+ oINTMSK处,oINTMSK定义如下,即地址为0x71200014和0x71300014:

0x71200014和0x71300014对应的寄存器如下:


中断使能清除寄存器全部设置1,清除所有中断使能,如下:


第4步,将0x0赋给通用寄存器r3,即r3 = 0x0;

第5步,分别使用str指令将r3存放的值赋给r0所存的地址+ oINTMOD处与r10所存的地址+ oINTMOD处,oINTMOD定义如下,即地址为0x7120000c和0x7130000c:

0x7120000c和0x7130000c对应的寄存器如下:


中断选择寄存器全部设清0,将所有中断设置为IRQ中断,如下:


第6步,将0x0赋给通用寄存器r3,即r3 = 0x0;

第7步,分别使用str指令将r3存放的值赋给r0所存的地址+ oVECTADDR处与r10所存的地址+ oVECTADDR处,oVECTADDR定义如下,即地址为0x71200F00和0x71300F00:

0x71200 F00和0x71300 F00对应的寄存器如下:


设置矢量地址寄存器(当前中断的矢量地址)为全0

继续往下度代码,如下:


初始化系统时钟,需要跳转到函数system_clock_init处执行,找到这个函数,在相同文件的第181行,如下:


首先是使用伪指令ldr将地址ELFIN_CLOCK_POWER_BASE存放到通用寄存器r0中,ELFIN_CLOCK_POWER_BASE定义如下: 0x7e00f000对应着系统控制基地址,APLL_CLOCK寄存器,如下:


接着下面有一个预编译判断,因为不是同步模式,跳到#else后代码执行,如下:


先是5个nop,用于延时。

接着使用ldr指令(这里不同于伪指令),将R0+ OTHERS_OFFSET地址处的内容传输到r1中,OTHERS_OFFSET定义如下:,即将地址0x7e00f900处的内容存放在r1中;

然后使用位清除指令将地址0x7e00f900处的内容第6、7位清零,继续存放在r1中;

再使用逻辑或指令将存放在r1的数值第6位置1;

最后使用str指令将r1中存放的数值赋给R0+ OTHERS_OFFSET地址处,即地址0x7e00f900处。

地址0x7e00f900对应着其他控制寄存器,如下:


将其第7位清0,第6位置1,就是配置系统时钟为异步模式、由APLL提供时钟,如下:


下面的代码是同步模式的,跳过不看,直接看#endif后面的代码,如下:


由注释可知这是配置系统时钟APLL MPLL EPLLd的,来看一下如何实现的:

第1步,将0xff00存放在通用寄存器r1中;

第2步,使用逻辑或指令将r1中存放的值低8位置,继续存放在r1中,此时r1=0xffff;

第3步,使用str指令分别将r1中的值赋给r0+APLL_LOCK_OFFSET地址处,r0+ MPLL_LOCK_OFFSET地址处和r0+ EPLL_LOCK_OFFSET地址处,定义如下:

地址分别是0x7e00f000、0x7e00f004、x7e00f008地址处,分别对应的寄存器如下:、


它们全部置1后,即它们的默认值,如下:

 



未完待续。。。。。。


猜你喜欢

转载自blog.csdn.net/u014754841/article/details/79846490