众所周知,ok6410开发板是一块基于s3c6410芯片的开发板,板载资源丰富。s3c6410是三星电子生产的基于arm11内核的芯片。本文旨在总结一下bootloader操作步骤,用于以后复习、查找。通过分析bootloader行业老大哥uboot代码,总结出要实现OK6410开发板的启动引导,只要实现如下的操作即可:
1. 设置异常向量表;
2. 设置处理器模式为svc模式;
3. 外设基地址初始化;
4. 关闭看门狗;
5. 关闭所有中断;
6. 关闭MMU和cache;
7. 初始化系统时钟;
8. 内存初始化;
9. 复制内存;
10.栈初始化;
11.清除bss段;
12.跳入c函数(点亮led)。
下面一步一步的介绍如何实现它们。
1. 设置异常向量表:
想要设置arm的异常向量表,首先得知道其有那些异常,查看arm架构参考手册Programmers’ Model 章节的A2.6 Exceptions我们知道其共有7中异常,如下图:
设置异常向量表,按我个人的理解其实就是将异常发生时要处理的地址指向PC即可,所以通过如下代码来实现:
.text
.global _start
_start:
b reset
ldr pc, _undifined_instruction @ 将pc指针指向地址_undefined_instruction处,即当该异常发生时处理器跳到标号undifined_instruction处执行
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
_undifined_instruction: .word undifined_instruction @ .word 伪指令,用于取地址,该段表示_undefined_instruction是一个指向标号
@ undifined_instruction处的32位地址
_software_interrupt: .word software_interrupt_prefetch_abort: .word prefetch_abort_data_abort: .word data_abort_not_used: .word not_used_irq: .word irq_fiq: .word resetundifined_instruction:nopsoftware_interrupt:nopprefetch_abort:nopdata_abort:nopnot_used:nopirq:nopfiq:nop
开头的.text告诉编译器下面的内容是代码段,.global _start告诉编译器_start是全局的。细心的人可能会发现,明明只有7种异常,为什么会有8个异常向量表,多出来的not_used是啥?从字面理解,它是一个未使用的异常,细心阅读arm架构参考手册就会发现有下面这么一段话
地址0x00000014处向量是一个未使用的,预留的向量表,从异常处理模式图也能发现0x00000010和0x00000018之间不是连续的,如下图:
为了异常的连续性,或者说是异常发生能够正确找到异常向量表,我们需要补全这个乡里表。
分析代码可知,首先跳转到reset处执行代码,那么在reset处都干啥呢了,我们可以找到reset处看一下,代码如下:
reset:
bl set_svc @ 设置cpu为svc模式
bl set_peri_port @ 外设基地址初始化
bl disable_watchdog @ 关闭看门狗
bl disable_interrupt @ 关闭所有中断
bl disable_mmu @ 关闭mmu和cache
bl init_clock @ 时钟初始化
bl mem_init @ 内存初始化
bl copy_to_ram @ 拷贝bl到内存
bl init_stack @ 栈初始化
bl clean_bss @ 初始化bss段
ldr pc, =main @ 进入c语言编程环境,main函数
原来后面的操作都是从reset处一次执行的,下面一步一步的介绍接下来的操作。
2.设置处理器模式为svc模式:bl set_svc
通过查看arm架构参考手册“A2.2 Processor modes ”章节可以知道,arm共有7种工作模式,如下:
要想cpu工作在svc模式下,首先我们来了解一下arm的程序状态寄存器CPSR,它可以用来改变工作模式等操作,CPSR格式如下:
它的低5位就是模式选择,通过查看模式为设置,可知只要将cpsr的第五位设置成0b10011就可以了,如下:
理论上,到此就可以了,但是,我们发现cpsr的I、F位是关于中断的,这里顺便也将其配置了,关闭IRQ和FIQ如下图:
代码如下:
@ set the cpu to SVC32 mode
set_svc:
mrs r0,cpsr @ 将程序状态寄存器取出保存在通用寄存器r0中
bic r0,r0,#0x1f @ 将从状态寄存器取出的值(r0)低5位清零后存入r0中
orr r0,r0,#0xd3 @ 将r0中的值的第0 1 4 6 7位置1后存入r0中,关闭中断与快速中断,进入svc模式
msr cpsr,r0 @ 将r0的中的值写入程序状态寄存器
mov pc, lr @ 返回调用的地方下一个地址处继续执行
2.外设基地址初始化:bl set_peri_port
这一步必须要做,而且必须在这一步做,否则cpu无法正常使用外设。要想初始化s3c6410的外设基地址,首先得知道外设基地址是从0x7000000开始的,同时还得了解一下arm11的协处理器p15的内容,参考arm11内核手册,找到系统协处理器章节,找到关于Peripheral Port Memory Remap Register的协处理器命令,如下:
跳到page 3-130章节处,查看其详细内容。该寄存器格式如下:
[31:12]:表示基地址,这里我们需要将其设成0x70000000,
[11:5]:不要考虑,保留默认值即可,
[4:0]:外设基地址的大小,s3c6410外设需要256M大小的内存。
查看Peripheral Port Memory Remap Register bit functions ,如下图:
通过上图可知只要往p15的c15中写入0x70000013即可。代码如下:
@ 初始化外设地址,必须做这一步,要不外设无法使用。
set_peri_port:
ldr r0, =0x70000000 @ 将基地址0x70000000存入寄存器r0中
orr r0, r0, #0x13 @ 将寄存器r0的值设为0x70000013
mcr p15,0,r0,c15,c2,4 @ 将寄存器r0的值传送到协处理器p15的c15寄存器中,参看arm11内核手册:c15, Peripheral Port Memory Remap Register
mov pc, lr @ 返回调用处继续往下执行
3.关闭看门狗:bl disable_watchdog
要想关闭看门狗,需要找到看门狗控制寄存器地址,通过查看s3c6410手册Watch Dog章节可以找到,如下:
看门狗控制寄存器位操作说明如下:
红框中的就是和关闭看门狗有关的位,只要将这几个位设置为0,即将看门狗控制寄存器设为0。代码如下:
@ Disable Watchdog 参看s3c6410手册:Watch Dog章节
#define pWTCON 0x7e004000 @ addr of watchdog timer control register (WTCON)
disable_watchdog:
ldr r0, =pWTCON @ 将看门狗控制寄存器地址写入寄存器r0中
mov r1, #0x0 @ 将0x0保存在寄存器r1中
str r1, [r0] @ 将0x0传送到看门狗控制寄存器中
mov pc, lr @ 返回调用处继续往下执行
4.关闭所有中断:bl disable_interrupt
可能有人会问,在前面设置cpu为svc工作模式时不是已经关闭了IRQ和FIQ吗,这里还需做啥操作呢?之前的只是关闭了中断,但是并没有清除中断使能位,这里就是进行这样的操作,清除所有中断的使能位。s3c6410共有两个中断控制器VIC0、VIC1。所以需要分别找到这两个INTERRUPT ENABLE CLEAR 寄存器,并且对其进行配置,如下图:
通过上图,可知只要将其设置为0 即可,代码如下:
@ Disable all interrupts (VIC0 and VIC1),参考s3c6410手册:Vectored Interrupt Controller章节
disable_interrupt:
mvn r1,#0x0
ldr r0,=0x71200014 @ VIC0INTENCLEAR地址
str r1,[r0] @ 将VIC0INTENCLEAR设为0
ldr r0,=0x71300014 @ VIC1INTENCLEAR地址
str r1,[r0] @ 将VIC1INTENCLEAR设为0
mov pc, lr @ 返回调用处继续往下执行
5.关闭mmu和cache
MMU是MemoryManagement Unit的缩写,中文名是内存管理单元,它是中央处理器(CPU)中用来管理虚拟存储器、物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权,多用户多进程操作系统。
Cache:高速缓冲存储器,是位于CPU和主存储器DRAM(DynamicRandomAccessMemory)之间,规模较小,但速度很高的存储器,通常由SRAM(StaticRandomAccessMemory静态存储器)组成。它是位于CPU与内存间的一种容量较小但速度很高的存储器。CPU的速度远高于内存,当CPU直接从内存中存取数据时要等待一定时间周期,而Cache则可以保存CPU刚用过或循环使用的一部分数据,如果CPU需要再次使用该部分数据时可从Cache中直接调用,这样就避免了重复存取数据,减少了CPU的等待时间,因而提高了系统的效率。3sc6410含有两个cache,分别是数据cache和指令cache。
这么好的东西为什么要关闭呢?对呀,为什么呀?那是因为相对于s3c6410的启动引导文件,并不需要使用到他们,开启了使用不当反而会导致异常产生。那么如何来关闭他们呢?这又的操作协处理器p15了。查看arm架构参考手册协处理器相应章节,找到cache相关协处理器命令,如下:
协处理器的命令都是死命令,想干啥找到了直接用就可以,是不是很爽呢?这里只是让cache失效了,但是cache具体使能位和mmu使能位还没有设置,找到协处理器的控制寄存器(c1),其中有这么一句话,如下红框中:
协处理器p15的控制寄存器c1格式如下:
详细说明如下:
因此我们只要设置[2:0]位位0即可,顺便关闭字节对齐检查功能,代码如下:
@ 关闭MMU和cache
disable_mmu:
mcr p15,0,r0,c7,c7,0 @ 使I/D cache全部失效 参考arm11内核手册:c7, Cache operations
mrc p15,0,r0,c1,c0,0 @ 取出协处理器p15的控制寄存器(c1)内容,存放在r0中 参考arm11内核手册:c1, Control Register
bic r0, r0, #0x00000007 @ 将r0的[2:0]设为0后存入r0
mcr p15,0,r0,c1,c0,0 @ 将r0的值传送到协处理器p15的控制寄存器(c1)中,完成mmu和cache配置
mov pc, lr @ 返回调用处继续往下执行
6.时钟初始化:bl init_clock
S3C6410可以使用外部晶振( XXTIpll )(默认为12MHZ)和外部时钟( XEXTCLK )两种方式输入时钟信号。它由跳线OM[0]决定。 S3C6410 默认的工作主频为12MHz(晶振频率), 3sc6410的系统时钟逻辑控制器可以产生系统必须的三种时钟信号,ARMCLK for CPU, HCLK for AXI/AHB-bus peripherals, and PCLK for the APB bus peripherals。s3c6410共有三种PLL,其中APLL用于产生ARMCLK,MPLL用于产生HCLK和PCLK,EPLL用于产生SCLK,音频相关的时钟,如下图:
框图下面有一段话,说明CLK_SRC寄存器是需要设置的,用来选择时钟输出源,如下:
下面的框图说明了系统时钟配置需要设置那些寄存器和如何设置参数,如下:
除了上面提到的寄存器需要配置,当然也少不了APLL控制寄存器和MPLL控制寄存器了,下面介绍一下这些寄存器。
从上图Power-on reset sequence中看到有一个Lock time,在这段时间(Lock Time)内,FCLK停振,CPU停止工作。Lock Time的长短由寄存器LOCKTIME设定 ,Lock Time之后,MPLL输出正常,CPU工作在新的FCLK下。 所以这个参数需要设定,但是由于这个时间很短(只有300us),所以不设定也不影响使用,如下图:
APLL_CON和MPLL_CON说明如下:
从上图可知,只需设置SDIV、PDIV、MDIV、ENABLE位就可以了,参考设定值如下:
CLK_DIV0寄存器说明如下:
用于设定ARMCLK、PCLK、HCLK等。
CLK_SRC说明如下:
这里只需要设定[2:0]就可以。
OTHERS寄存器说明如下:
这里我们只需设定[7:6]即可。代码如下:
@ 初始化时钟 参考s3c6410手册系统控制章节、时钟相关章节
#define CLK_DIV0 0x7e00f020 @ 时钟分频寄存器0地址
#define OTHERS 0x7e00f900 @ 其他控制寄存器地址
#define MPLL_CON 0x7e00f010 @ MPLL控制寄存器地址
#define APLL_CON 0x7e00f00c @ APLL控制寄存器地址
#define CLK_SRC 0x7e00f01c @ 时钟源选择寄存器地址
#define DIV_VAL ((0x0<<0)|(0x1<<9)|(0x1<<8)|(0x3<<12)) @ 分频设置值 2b0000_0000_0000_0000_0011_0011_0000_0000 参考uboot设置
#define PLL_VAL ((1<<31)|(266<<16)|(3<<8)|(1<<0)) @ PLL设置值 2b1000_0001_0000_1010_0000_0011_0000_0001 参考uboot设置
init_clock:
@ 设置时钟分频系数
ldr r0, =CLK_DIV0 @ 将时钟分频寄存器0的地址存入寄存器r0中
ldr r1, =DIV_VAL @ 将需要配置到时钟分频寄存器0的数值存入寄存器r1中
str r1, [r0] @ 将r1的数值传送到时钟分频寄存器0中
@ 设置cpu为异步模式,将OTHERS寄存器内容设置为0xc0
ldr r0, =OTHERS @ 将OTHERS寄存地址存入寄存器r0中
ldr r1, [r0] @ 将其他控制寄存器中的数据读出,存入r1中
bic r1,r1,#0xc0 @ 将r1中的数值第6 7 bit位清0后再存入r1中
str r1, [r0] @ 将r1中的数值传送到OTHERS寄存器中,Asynchronous mode 和 MOUTmpll
@ 设置APLL时钟频率 533MHz
ldr r0, =APLL_CON @ 将APLL控制寄存器地址写入r0中
ldr r1, =PLL_VAL @ 将需要配置的PLL数值存入寄存器r1中
str r1, [r0] @ 将r1中的数值写入APLl控制寄存器中
@ 设置MPLL时钟频率 533MHz
ldr r0, =MPLL_CON @ 将MPLL控制寄存器地址写入r0中
ldr r1, =PLL_VAL @ 将需要配置的PLL数值存入寄存器r1中
str r1, [r0] @ 将r1中的数值写入MPLL控制寄存器中
@ 设置时钟源选择控制寄存器,使用APLL和MPLL输出时钟。
ldr r0, =CLK_SRC @ 将时钟源选择寄存器地址写入r0中
mov r1, #0x3 @ 将0x03写入寄存器r1中
str r1, [r0] @ 将r1中的数据写入时钟源选择控制寄存器中
mov pc, lr @ 返回调用处继续往下执行
7.内存初始化:bl mem_init
内存由于具备访问速度快,访问方式简单等优点,成为了PC或者是嵌入式硬件平台上不可或缺的元件。在开始学习如何使用内存之前,非常有必要先了解一下内存的分类:
1.DRAM:它的基本原件是小电容,电容可以在两个极板上保留电荷,但是需要定期的充电(刷新),否则数据会丢失。缺点:由于要定期刷新存储介质,存取速度较慢。
2.SRAM:它是一种具有静止存取功能的内存,不需要定期刷新电路就能保存它内部存储的数据。其优点:存取速度快; 但是缺点是:功耗大,成本高。常用作存储容量不高,但存取速度快的场合,比如steppingstone。
在嵌入式硬件体系中,除了CPU内部的”垫脚石”采用SRAM外,板载内存一般会采用DRAM,而DRAM又可以分为SDRAM,DDR,DDR2等。
SDRAM(Synchronous Dynamic Random AccessMemory):同步动态随机存储器.
同步: 内存工作需要有同步时钟,内部的命令的发送与数据的传输都以该时钟为基准。
动态:存储阵列需要不断的刷新来保证数据不丢失。
随机:是指数据不是线性依次存储,而是自由指定地址进行数据读写。
DDR (Double Data Rate SDRAM),即“双倍速率同步动态随机存储器”。与早期的SDRAM相比,DDR 除了可以在时钟脉冲的上升沿传输数据,还可以在下降沿传输信号,这意味着在相同的工作频率下,DDR 的理论传输速率为SDRAM的两倍。DDR2 则在DDR 的基础上再次进行了改进,使得数据传输速率在DDR 的基础上再次翻倍。6410开发板通常采用DDR内存 。
内存的内部如同表格,数据就存放在每个单元格中。数据读写时,先指定行号(行地址),再指定列号(列地址) ,我们就可以准确地找到所需要的单元格。而这张表格的称为:Logical Bank(L-Bank)。 如下图:
由于技术、成本等原因,一块内存不可能把所有的单元格都做到一个L-Bank,现在内存内部基本都会分割成4
个L-Bank。 S3C6410处理器拥32位地址总线,其寻址空间为4GB。其中高2GB为保留区,低2GB区域又可划分
为两部分:主存储区和外设区。如下左图:
主存储去又可以分为:Boot镜像区、内部存储区、静态存储区、保留区、动态存储区,如上右图 。Boot镜像区:这个区域的作用正如它的名字所述,是用来启动ARM系统的。但是这个区域并没有固定的存储介质与之对应。而是通过修改启动选项,把不同的启动介质映射到该区域。比如说选择了IROM 启动方式后,就把IROM映射到该区域。
内部存储区:这个区域对应着内部的内存地址,iROM和SRAM都是分布在这个区间。0x08000000~0x0bffffff对应着内部ROM,但是IROM实际只有32KB,选择从IROM启动的时候,首先运行就是这里面的程序BL0,这部分代码由三星固化。0x0c000000~0x0fffffff对应内部SRAM,实际就是8KB的Steppingstone。
静态存储区:这个区域用于访问挂在外部总线上的设备,比如说NOR flash、oneNand等。这个区域被分割为6个bank,每个bank为128MB,数据宽度最大支持16bit,每个bank由片选Xm0CS[0]~Xm0CS[5] 选中。
动态存储区:该区域从0x50000000~0x6fffffff,又分为2个区间,分别占256MB,可以片选Xm1CS[0]~Xm1CS[1]来进行着2个区间的选择。我们6410开发板上256MB的DDR内存就安排在这个区域,这也就是为什么6410的内存地址是从0x50000000开始的原因。
OK6410内存芯片硬件连接如下:
从上图可以知道,它是通过两片内存芯片组合成32位数据的,分别使用s3c6410的前16位和后16位数据线。
查看s3c6410手册DRAM Controller 章节,可以发现有关于内存初始化操作步骤的说明,如下:
所以就可以参考上面的操作步骤来实现内存的初始化。
第1步:配置DRAM CONTROLLER COMMAND REGISTER为2b011及为0x4,让其进入配置模式,寄存器说明如下:
第2步:配置内存时序寄存器、片选配置寄存器、用户配置寄存器;内存时序寄存器比较多,这里不再一一列举,大家可以查看s3c6410手册,片选配置寄存器说明如下:
用户配置寄存器说明如下:
第3步:等待200us,让SDRAM电源和时钟稳定,但是,当cpu启动时电源和时钟已经稳定。呵呵,就是不需要了呗,绕这么大一圈,可能是三星公司有病吧;
第4步:执行内存初始化,说了半天才内存初始化,之前干啥啦?不要吵吵,之前是sdram的初始化,因为OK6410使用的是mobile DDR sdram,所以参考5.4.2的ddr/mobile DDR sdram初始化步骤,根据说明,其实就是重复设置P1DIRECTCMD寄存器(直接命令寄存器)的Memory command位,即[19:18]位。分别设置为2b11 2b00 2b01 2b01 2b10 2b10使其运行中nop、Prechargeall 、Autorefresh 、MRS、EMRS。直接命令寄存器说明如下:
第5步:设置P1MEMCCMD寄存器(内存控制命令寄存器)的Memc_cmd位位3b000,让DRAM控制器进入准备好模式,内存控制命令寄存器说明如下:
第6步:检测DRAM控制器是否进入准备模式,详见代码,如下:
.text
.global mem_init
mem_init:
ldr r0, =0x7e00f120 @配置内存系统子程序寄存器地址
mov r1, #0x8 @将0x8存入r1寄存器
str r1, [r0] @将r1寄存器里的数据写入配置内存系统子程序寄存器中
ldr r0, =0x7e001004 @内存控制命令寄存器
mov r1, #0x4 @根据手册得知需要先进入配置模式
str r1, [r0] @将寄存器r1内容写入内存控制命令寄存器中
ldr r0, =0x7e001010 @刷新寄存器地址
ldr r1, =( ( 7800 / ( 1000000000/133000000 ) + 1 ) ) @设置刷新时间
str r1, [r0] @将寄存器r1内容写入刷新寄存器寄存器中
ldr r0, =0x7e001014 @CAS latency寄存器
mov r1, #(3 << 1) @r1=0x6
str r1, [r0] @将寄存器r1内容(0x06)写入CAS latency寄存器中
ldr r0, =0x7e001018 @t_DQSS寄存器
mov r1, #0x1 @r1=0x1
str r1, [r0] @将寄存器r1内容(0x01)写入t_DQSS寄存器中
ldr r0, =0x7e00101c @T_MRD寄存器
mov r1, #0x2 @r1=0x2
str r1, [r0] @将寄存器r1内容(0x02)写入T_MRD寄存器中
ldr r0, =0x7e001020 @t_RAS寄存器
ldr r1, =( ( 45 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=6.95
str r1, [r0] @将寄存器r1内容(6.95)写入t_RAS寄存器中
ldr r0, =0x7e001024 @t_RC寄存器
ldr r1, =( ( 68 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=10.044
str r1, [r0] @将寄存器r1内容(10.044)写入t_RC寄存器中
ldr r0, =0x7e001028 @t_RCD寄存器
ldr r1, =( ( 23 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=3.99
str r1, [r0] @将寄存器r1内容(3.99)写入t_RCD寄存器中
ldr r0, =0x7e00102c @t_RFC寄存器
ldr r1, =( ( 80 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=11.64
str r1, [r0] @将寄存器r1内容(11.64)写入t_RFC寄存器中
ldr r0, =0x7e001030 @t_RP寄存器
ldr r1, =( ( 23 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=3.99
str r1, [r0] @将寄存器r1内容(3.99)写入t_RP寄存器中
ldr r0, =0x7e001034 @t_rrd寄存器
ldr r1, =( ( 15 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=2.995
str r1, [r0] @将寄存器r1内容(2.995)写入t_rrd寄存器中
ldr r0, =0x7e001038 @t_wr寄存器
ldr r1, =( ( 15 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=2.995
@ ldr r2, [r0]
str r1, [r0] @将寄存器r1内容(2.995)写入T_MRD寄存器中
ldr r0, =0x7e00103c @t_wtr寄存器
mov r1, #0x07 @r1=0x07
str r1, [r0] @将寄存器r1内容(0x07)写入T_MRD寄存器中
ldr r0, =0x7e001040 @t_xp寄存器
mov r1, #0x02 @r1=0x02
str r1, [r0] @将寄存器r1内容(0x02)写入t_xp寄存器中
ldr r0, =0x7e001044 @t_xsr寄存器
ldr r1, =( ( 120 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=16.69
str r1, [r0] @将寄存器r1内容(16.69)写入t_xsr寄存器中
ldr r0, =0x7e001048 @t_esr寄存器
ldr r1, =( ( 120 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=16.69
str r1, [r0] @将寄存器r1内容(16.69)写入t_esr寄存器中
ldr r0, =0x7e00100c @内存控制配置寄存器 P1MEMCFC
ldr r1, =0x00010012 @配置控制器 r1=2b0000_0000_0000_0001_0000_0000_0001_0010
str r1, [r0] @将寄存器r1内容(0x10012)写入P1MEMCFC寄存器中
ldr r0, =0x7e00104c @32位DRAM配置控制寄存器
ldr r1, =0x0b45 @r1=2b0000_0000_0000_0000_0000_1011_0100_0101 选择Mobile DDR SDRAM
str r1, [r0] @将寄存器r1内容(0x0b45)写入T寄存器中
ldr r0, =0x7e001200 @片选寄存器
ldr r1, =0x150f8 @r1=0x150f8 r1=2b0000_0000_0000_0001_0101_0000_1111_1000 Bank-Row-Column organization
str r1, [r0] @将寄存器r1内容(0x02)写入T_MRD寄存器中
ldr r0, =0x7e001304 @用户配置寄存器
mov r1, #0x0 @r1=0x0
str r1, [r0] @将寄存器r1内容(0x00)写入用户配置寄存器中
ldr r0, =0x7e001008 @直接命令寄存器
ldr r1, =0x000c0000 @r1=2b0000_0000_0000_1100_0000_0000_0000_0000 makes DRAM Controller issue ‘NOP’ memory
str r1, [r0] @将寄存器r1内容(0xc0000)写入直接命令寄存器中
ldr r1, =0x00000000 @r1=0 makes DRAM Controller issue‘PrechargeAll’ memory command.
str r1, [r0] @将寄存器r1内容(0x0)写入直接命令寄存器中
ldr r1, =0x00040000 @r1=2b0000_0000_0000_0100_0000_0000_0000_0000 makes DRAM Controller issue ‘Autorefresh’ memory command.
str r1, [r0] @将寄存器r1内容(0x40000)写入直接命令寄存器中
ldr r1, =0x000a0000 @r1=2b0000_0000_0000_1010_0000_0000_0000_0000 makes DRAM Controller issue ‘MRS’ memory command.
str r1, [r0] @将寄存器r1内容(0xa0000)写入直接命令寄存器中
ldr r1, =0x00080032 @r1=2b0000_0000_0000_1000_0000_0000_0011_0010
str r1, [r0] @将寄存器r1内容(0x80032)写入直接命令寄存器中 makes DRAM Controller issue ‘EMRS’ memory command.
ldr r0, =0x7e001004 @内存控制命令寄存器
mov r1, #0x0 @根据手册得知需要进入ready模式
str r1, [r0] @将寄存器r1内容写入内存控制命令寄存器中
@检查内存初始化是否准备好
check_dmc1_ready:
ldr r0, =0x7e001000 @DRAM控制状态寄存器
ldr r1, [r0] @将DRAM控制状态寄存器中内容写入寄存器r1中
mov r2, #0x3 @将0x03写入寄存器r2中
and r1, r1, r2 @将r1和r2与运算后存在r1中,即只保持读出的值的[1:0]位
cmp r1, #0x1 @将读出的值与0x01比较
bne check_dmc1_ready @若不相等,跳转到标号check_dmc1_ready处继续执行,检查内存状态
nop
mov pc, lr @返回调用处继续往下执行
8.拷贝bl到内存中:bl copy_to_arm
要想拷贝bl到内存中,我们首先的看一下s3c6410的启动流程图,如下:
解释如下:
整个流程如下:首先是执行芯片固化在iROM中的bl0代码,该段代码将Booting Device中的bl1搬移到Stepping stone中,但是Stepping stone只有8k,一个完整的代码代码量会超过8K,那样就没法运行了,所以bl1这段代码其中就有一个功能将大部分代码搬移到内存中去执行,这里要实现的就是这个功能,只不过是将从Booting device中bl1开始的的4K代码移过去。代码如下:
@ 代码搬移,拷贝bl到内存中
copy_to_ram:
ldr r0, =0x0c000000 @ Stepping Stone (Boot Loader)基地址,即要搬移的源地址存入r0 起点
ldr r1, =0x50008000 @ 内存地址,要搬移的目标地址存入r1 终点
add r3, r0, #1024*4 @ 将r0处的地址加4k大小存入r3,要搬移的内存Size 4k, 大小
copy_loop: @ 等待搬移完成
ldr r2, [r0], #4 @ 将地址0x0c000000增加4后(0x0c000004)地址数据存入r2
str r2, [r1], #4 @ 将r2的数据传送到r1+4地址处(0x500080004)
cmp r0, r3 @ 比较r0和r3处地址
bne copy_loop @ 若不相等,跳到标号copy_loop处继续执行
mov pc, lr @ 返回调用处继续往下执行
9.栈初始化:bl init_stack
栈是一种具有后进先出性质的数据组织方式,也就是说后存放的先取出,先存放的后取出。栈底是第一个进栈的数据所处的位置,栈顶是最后一个进栈的数据所处的位置 。如下左图:
根据SP指针指向的位置,栈可以分为满栈和空栈。
1. 满栈:当堆栈指针SP总是指向最后压入堆栈的数据
2. 空栈:当堆栈指针SP总是指向下一个将要放入数据的空位置。ARM采用满栈! 如上图中:
根据SP指针移动的方向,栈可以分为升栈和降栈。
1. 升栈:随着数据的入栈,SP指针从低地址->高地址移动;
2. 降栈:随着数据的入栈,SP指针从高地址->低地址移动。ARM采用降栈! 如下图:
栈帧(stack frame)就是一个函数所使用的那部分栈,所有函数的栈帧串起来就组成了一个完整的栈。栈帧的两个边界分别由fp(r11)和sp(r13)来限定。 如下图:总结下来,栈主要用来保存局部变量、传递参数、保存寄存器值,显然初始化栈很重要。代码如下:
@ 栈初始化 64M内存用于栈
init_stack:
ldr sp, =0x54000000 @ 将0x54000000写入sp寄存器中,栈大小64M 0x5400000000-0x500000000
mov pc, lr @ 返回调用处继续往下执行
虽然就是这么一小段代码,但是大家不能小看他,没有他,后面的c语言开发环境就无从说起了。
10.清除bss段:bl clean_bss
bss段主要存放的是未初始化的全局变量,为了防止影响以后的使用,要将其初始化为0。也是为C语言环境做准备,代码如下:
@ 清除bss段
clean_bss:
ldr r0, =bss_start @ bss段开始地址,参见链接器脚本
ldr r1, =bss_end @ bss段结束地址
cmp r0, r1 @ 比较bss段开始与结束地址
moveq pc, lr @ 若相等,返回调用处继续往下执行
clean_loop: @ 若不相等,执行下面的代码
mov r2, #0 @ r2=0
str r2, [r0], #4 @ 将r2的值(0)写入r0所指定的地址中,同时r0指定的地址+4后存入r0
cmp r0, r1 @ 比较bss段新开始与结束地址
bne clean_loop @ 若不相等,跳转到clean_loop标号处执行
mov pc, lr @ 否则,返回调用处继续执行
11.进入c编程环境:ldr pc, =main
到此,也就是海宽任鱼跃,天高任鸟飞了。在c语言环境中,大家不用再为记不住汇编命令感到头疼了,是不是很爽呢?废话少说。上一段c语言环境下的点亮led代码:
#define GPKCON (volatile unsigned long*)0x7F008820
#define GPKDAT (volatile unsigned long*)0x7F008824
// 延时函数
void delay()
{
int i = 0;
int j = 0;
for(i = 0; i < 10000; i++)
{
for(j = 0; j < 110; j++)
{
;
}
}
}
int main()
{
int val = 0x1e;
*(GPKCON) = 0x1111;
while(1)
{
*(GPKDAT) = val;
delay();
val = val<<1;
if((val&0x10) != 0x10)
val = 0x1f;
}
return 0;
}
本节内容也就讲完了,最后贴上整体代码:
start.S
.text
.global _start
_start:
b reset
ldr pc, _undifined_instruction @ 未知指令异常
ldr pc, _software_interrupt @ 软件中断
ldr pc, _prefetch_abort @ 预取异常
ldr pc, _data_abort @ 数据终止异常
ldr pc, _not_used @ 未使用,预留
ldr pc, _irq @ 中断
ldr pc, _fiq @ 快速中断
_undifined_instruction: .word undifined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word reset
undifined_instruction:
nop
software_interrupt:
nop
prefetch_abort:
nop
data_abort:
nop
not_used:
nop
irq:
nop
fiq:
nop
reset:
bl set_svc @ 设置cpu为svc模式
bl set_peri_port @ 外设基地址初始化
bl disable_watchdog @ 关闭看门狗
bl disable_interrupt @ 关闭所有中断
bl disable_mmu @ 关闭mmu和cache
bl init_clock @ 时钟初始化
bl mem_init @ 内存初始化
bl copy_to_ram @ 拷贝bl到内存中
bl init_stack @ 栈初始化
bl clean_bss @ 初始化bss段
ldr pc, =main @ 进入c语言编程环境,main函数
@bl light_led
@ set the cpu to SVC32 mode
set_svc:
mrs r0,cpsr @ 将程序状态寄存器取出保存在通用寄存器r0中,参看am架构参考手册:A2.5 Program status registers
bic r0,r0,#0x1f @ 将从状态寄存器取出的值(r0)低5位清零后存入r0中
orr r0,r0,#0xd3 @ 将r0中的值的第0 1 4 6 7位置1后存入r0中,关闭中断与快速中断,进入svc模式
msr cpsr,r0 @ 将r0的中的值写入程序状态寄存器
mov pc, lr @ 返回调用处继续往下执行
@ 初始化外设地址,必须做这一步,要不外设无法使用。
set_peri_port:
ldr r0, =0x70000000 @ 将基地址0x70000000存入寄存器r0中
orr r0, r0, #0x13 @ 将寄存器r0的值设为0x70000013
mcr p15,0,r0,c15,c2,4 @ 将寄存器r0的值传送到协处理器p15的c15寄存器中,参看arm11内核手册:c15, Peripheral Port Memory Remap Register
mov pc, lr @ 返回调用处继续往下执行
@ Disable Watchdog 参看s3c6410手册:Watch Dog章节
#define pWTCON 0x7e004000 @ addr of watchdog timer control register (WTCON)
disable_watchdog:
ldr r0, =pWTCON @ 将看门狗控制寄存器地址写入寄存器r0中
mov r1, #0x0 @ 将0x0保存在寄存器r1中
str r1, [r0] @ 将0x0传送到看门狗控制寄存器中
mov pc, lr @ 返回调用处继续往下执行
@ Disable all interrupts (VIC0 and VIC1),参考s3c6410手册:Vectored Interrupt Controller章节
disable_interrupt:
mvn r1,#0x0
ldr r0,=0x71200014 @ VIC0INTENCLEAR地址
str r1,[r0] @ 将VIC0INTENCLEAR设为0
ldr r0,=0x71300014 @ VIC1INTENCLEAR地址
str r1,[r0] @ 将VIC1INTENCLEAR设为0
mov pc, lr @ 返回调用处继续往下执行
@ 关闭MMU和cache
disable_mmu:
mcr p15,0,r0,c7,c7,0 @ 使I/D cache全部失效 参考arm11内核手册:c7, Cache operations
mrc p15,0,r0,c1,c0,0 @ 取出协处理器p15的控制寄存器(c1)内容,存放在r0中 参考arm11内核手册:c1, Control Register
bic r0, r0, #0x00000007 @ 将r0的[2:0]设为0后存入r0
mcr p15,0,r0,c1,c0,0 @ 将r0的值传送到协处理器p15的控制寄存器(c1)中,完成mmu和cache配置
mov pc, lr @ 返回调用处继续往下执行
@ 初始化时钟 参考s3c6410手册系统控制章节、时钟相关章节
#define CLK_DIV0 0x7e00f020 @ 时钟分频寄存器0地址
#define OTHERS 0x7e00f900 @ 其他控制寄存器地址
#define MPLL_CON 0x7e00f010 @ MPLL控制寄存器地址
#define APLL_CON 0x7e00f00c @ APLL控制寄存器地址
#define CLK_SRC 0x7e00f01c @ 时钟源选择寄存器地址
#define DIV_VAL ((0x0<<0)|(0x1<<9)|(0x1<<8)|(0x3<<12)) @ 分频设置值 2b0000_0000_0000_0000_0011_0011_0000_0000 参考uboot设置
#define PLL_VAL ((1<<31)|(266<<16)|(3<<8)|(1<<0)) @ PLL设置值 2b1000_0001_0000_1010_0000_0011_0000_0001 参考uboot设置
init_clock:
@ 设置时钟分频系数
ldr r0, =CLK_DIV0 @ 将时钟分频寄存器0的地址存入寄存器r0中
ldr r1, =DIV_VAL @ 将需要配置到时钟分频寄存器0的数值存入寄存器r1中
str r1, [r0] @ 将r1的数值传送到时钟分频寄存器0中
@ 设置cpu为异步模式,将OTHERS寄存器内容设置为0xc0
ldr r0, =OTHERS @ 将OTHERS寄存地址存入寄存器r0中
ldr r1, [r0] @ 将其他控制寄存器中的数据读出,存入r1中
bic r1,r1,#0xc0 @ 将r1中的数值第6 7 bit位清0后再存入r1中
str r1, [r0] @ 将r1中的数值传送到OTHERS寄存器中,Asynchronous mode 和 MOUTmpll
@ 设置APLL时钟频率 533MHz
ldr r0, =APLL_CON @ 将APLL控制寄存器地址写入r0中
ldr r1, =PLL_VAL @ 将需要配置的PLL数值存入寄存器r1中
str r1, [r0] @ 将r1中的数值写入APLl控制寄存器中
@ 设置MPLL时钟频率 533MHz
ldr r0, =MPLL_CON @ 将MPLL控制寄存器地址写入r0中
ldr r1, =PLL_VAL @ 将需要配置的PLL数值存入寄存器r1中
str r1, [r0] @ 将r1中的数值写入MPLL控制寄存器中
@ 设置时钟源选择控制寄存器,使用APLL和MPLL输出时钟。
ldr r0, =CLK_SRC @ 将时钟源选择寄存器地址写入r0中
mov r1, #0x3 @ 将0x03写入寄存器r1中
str r1, [r0] @ 将r1中的数据写入时钟源选择控制寄存器中
mov pc, lr @ 返回调用处继续往下执行
@ 代码搬移,拷贝bl到内存中
copy_to_ram:
ldr r0, =0x0c000000 @ Stepping Stone (Boot Loader)基地址,即要搬移的源地址存入r0
ldr r1, =0x50008000 @ 内存地址,要搬移的目标地址存入r1
add r3, r0, #1024*4 @ 将r0处的地址加4k大小存入r3,要搬移的内存Size 4k,
copy_loop: @ 等待搬移完成
ldr r2, [r0], #4 @ 将地址0x0c000000处数据存入r2后,在增加4后(0x0c000004)存入r0中
str r2, [r1], #4 @ 将r2的数据传送到r1指定的地址处后,在+4(0x500080004)存入r1中
cmp r0, r3 @ 比较r0和r3处地址
bne copy_loop @ 若不相等,跳到标号copy_loop处继续执行
mov pc, lr @ 返回调用处继续往下执行
@ 栈初始化 64M内存用于栈
init_stack:
ldr sp, =0x54000000 @ 将0x54000000写入sp寄存器中,栈大小64M 0x5400000000-0x500000000
mov pc, lr @ 返回调用处继续往下执行
@ 清除bss段
clean_bss:
ldr r0, =bss_start @ bss段开始地址,参见链接器脚本
ldr r1, =bss_end @ bss段结束地址
cmp r0, r1 @ 比较bss段开始与结束地址
moveq pc, lr @ 若相等,返回调用处继续往下执行
clean_loop: @ 若不相等,执行下面的代码
mov r2, #0 @ r2=0
str r2, [r0], #4 @ 将r2的值(0)写入r0所指定的地址中,同时r0指定的地址+4后存入r0
cmp r0, r1 @ 比较bss段新开始与结束地址
bne clean_loop @ 若不相等,跳转到clean_loop标号处执行
mov pc, lr @ 否则,返回调用处继续执行
@ 点亮led
#define GPMCON 0x7F008820
#define GPMDAT 0x7F008824
#define GPMPUD 0x7F008828
#define LED1 0x1E
#define LED2 0x1D
#define LED3 0x1B
#define LED4 0x17
light_led:
ldr r0,=GPMCON
ldr r1,=0x1111
str r1,[r0]
ldr r0,=GPMPUD
ldr r1,=0x55
str r1,[r0]
ldr r0,=GPMDAT
mov r1,#0x0e
str r1,[r0]
mov pc,lr
其中有一段汇编下的点亮led的代码,大家正好可以和c语言环境下的代码对比一下,看一下孰优孰劣。可能大家发现,怎么内存初始化的代码不见了,别急,马上就来:
内存初始化代码 mem.S
.text
.global mem_init
mem_init:
ldr r0, =0x7e00f120 @配置内存系统子程序寄存器地址
mov r1, #0x8 @将0x8存入r1寄存器
str r1, [r0] @将r1寄存器里的数据写入配置内存系统子程序寄存器中
ldr r0, =0x7e001004 @内存控制命令寄存器
mov r1, #0x4 @根据手册得知需要先进入配置模式
str r1, [r0] @将寄存器r1内容写入内存控制命令寄存器中
ldr r0, =0x7e001010 @刷新寄存器地址
ldr r1, =( ( 7800 / ( 1000000000/133000000 ) + 1 ) ) @设置刷新时间
str r1, [r0] @将寄存器r1内容写入刷新寄存器寄存器中
ldr r0, =0x7e001014 @CAS latency寄存器
mov r1, #(3 << 1) @r1=0x6
str r1, [r0] @将寄存器r1内容(0x06)写入CAS latency寄存器中
ldr r0, =0x7e001018 @t_DQSS寄存器
mov r1, #0x1 @r1=0x1
str r1, [r0] @将寄存器r1内容(0x01)写入t_DQSS寄存器中
ldr r0, =0x7e00101c @T_MRD寄存器
mov r1, #0x2 @r1=0x2
str r1, [r0] @将寄存器r1内容(0x02)写入T_MRD寄存器中
ldr r0, =0x7e001020 @t_RAS寄存器
ldr r1, =( ( 45 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=6.95
str r1, [r0] @将寄存器r1内容(6.95)写入t_RAS寄存器中
ldr r0, =0x7e001024 @t_RC寄存器
ldr r1, =( ( 68 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=10.044
str r1, [r0] @将寄存器r1内容(10.044)写入t_RC寄存器中
ldr r0, =0x7e001028 @t_RCD寄存器
ldr r1, =( ( 23 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=3.99
str r1, [r0] @将寄存器r1内容(3.99)写入t_RCD寄存器中
ldr r0, =0x7e00102c @t_RFC寄存器
ldr r1, =( ( 80 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=11.64
str r1, [r0] @将寄存器r1内容(11.64)写入t_RFC寄存器中
ldr r0, =0x7e001030 @t_RP寄存器
ldr r1, =( ( 23 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=3.99
str r1, [r0] @将寄存器r1内容(3.99)写入t_RP寄存器中
ldr r0, =0x7e001034 @t_rrd寄存器
ldr r1, =( ( 15 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=2.995
str r1, [r0] @将寄存器r1内容(2.995)写入t_rrd寄存器中
ldr r0, =0x7e001038 @t_wr寄存器
ldr r1, =( ( 15 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=2.995
@ ldr r2, [r0]
str r1, [r0] @将寄存器r1内容(2.995)写入T_MRD寄存器中
ldr r0, =0x7e00103c @t_wtr寄存器
mov r1, #0x07 @r1=0x07
str r1, [r0] @将寄存器r1内容(0x07)写入T_MRD寄存器中
ldr r0, =0x7e001040 @t_xp寄存器
mov r1, #0x02 @r1=0x02
str r1, [r0] @将寄存器r1内容(0x02)写入t_xp寄存器中
ldr r0, =0x7e001044 @t_xsr寄存器
ldr r1, =( ( 120 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=16.69
str r1, [r0] @将寄存器r1内容(16.69)写入t_xsr寄存器中
ldr r0, =0x7e001048 @t_esr寄存器
ldr r1, =( ( 120 / ( 1000000000 / 133000000 ) + 1 ) ) @r1=16.69
str r1, [r0] @将寄存器r1内容(16.69)写入t_esr寄存器中
ldr r0, =0x7e00100c @内存控制配置寄存器 P1MEMCFC
ldr r1, =0x00010012 @配置控制器 r1=2b0000_0000_0000_0001_0000_0000_0001_0010
str r1, [r0] @将寄存器r1内容(0x10012)写入P1MEMCFC寄存器中
ldr r0, =0x7e00104c @32位DRAM配置控制寄存器
ldr r1, =0x0b45 @r1=2b0000_0000_0000_0000_0000_1011_0100_0101 选择Mobile DDR SDRAM
str r1, [r0] @将寄存器r1内容(0x0b45)写入T寄存器中
ldr r0, =0x7e001200 @片选寄存器
ldr r1, =0x150f8 @r1=0x150f8 r1=2b0000_0000_0000_0001_0101_0000_1111_1000 Bank-Row-Column organization
str r1, [r0] @将寄存器r1内容(0x02)写入T_MRD寄存器中
ldr r0, =0x7e001304 @用户配置寄存器
mov r1, #0x0 @r1=0x0
str r1, [r0] @将寄存器r1内容(0x00)写入用户配置寄存器中
ldr r0, =0x7e001008 @直接命令寄存器
ldr r1, =0x000c0000 @r1=2b0000_0000_0000_1100_0000_0000_0000_0000 makes DRAM Controller issue ‘NOP’ memory
str r1, [r0] @将寄存器r1内容(0xc0000)写入直接命令寄存器中
ldr r1, =0x00000000 @r1=0 makes DRAM Controller issue‘PrechargeAll’ memory command.
str r1, [r0] @将寄存器r1内容(0x0)写入直接命令寄存器中
ldr r1, =0x00040000 @r1=2b0000_0000_0000_0100_0000_0000_0000_0000 makes DRAM Controller issue ‘Autorefresh’ memory command.
str r1, [r0] @将寄存器r1内容(0x40000)写入直接命令寄存器中
ldr r1, =0x000a0000 @r1=2b0000_0000_0000_1010_0000_0000_0000_0000 makes DRAM Controller issue ‘MRS’ memory command.
str r1, [r0] @将寄存器r1内容(0xa0000)写入直接命令寄存器中
ldr r1, =0x00080032 @r1=2b0000_0000_0000_1000_0000_0000_0011_0010
str r1, [r0] @将寄存器r1内容(0x80032)写入直接命令寄存器中 makes DRAM Controller issue ‘EMRS’ memory command.
ldr r0, =0x7e001004 @内存控制命令寄存器
mov r1, #0x0 @根据手册得知需要进入ready模式
str r1, [r0] @将寄存器r1内容写入内存控制命令寄存器中
@检查内存初始化是否准备好
check_dmc1_ready:
ldr r0, =0x7e001000 @DRAM控制状态寄存器
ldr r1, [r0] @将DRAM控制状态寄存器中内容写入寄存器r1中
mov r2, #0x3 @将0x03写入寄存器r2中
and r1, r1, r2 @将r1和r2与运算后存在r1中,即只保持读出的值的[1:0]位
cmp r1, #0x1 @将读出的值与0x01比较
bne check_dmc1_ready @若不相等,跳转到标号check_dmc1_ready处继续执行,检查内存状态
nop
mov pc, lr @返回调用处继续往下执行
不要问我为什么这么设置,我也不是很清楚,参考uboot设置的,总之能用,以后慢慢去理解吧!
C语言环境下的代码:
#define GPKCON (volatile unsigned long*)0x7F008820
#define GPKDAT (volatile unsigned long*)0x7F008824
// 延时函数
void delay()
{
int i = 0;
int j = 0;
for(i = 0; i < 10000; i++)
{
for(j = 0; j < 110; j++)
{
;
}
}
}
int main()
{
int val = 0x1e;
*(GPKCON) = 0x1111;
while(1)
{
*(GPKDAT) = val;
delay();
val = val<<1;
if((val&0x10) != 0x10)
val = 0x1f;
}
return 0;
}
终于写完了,也算是给自己这段时间通宵达旦学习一个交代吧!感觉自己没有白白浪费时间,还是学到一点东西,这些记不住,以后只能通过这篇博文复习巩固了。加油,继续接下来的学习。