04. 代码重定位 & SDRAM初始化

1. C语言环境初始化

1)C语言运行所需环境

① 设置合适的栈(C语言函数的运行高度依赖函数栈)
② bss段清零
这些环境均由汇编代码提供,设置完成后才可以进入C语言运行环境

2)初始化栈

A. 栈的概念

① 根据AAPCS规则,ARM使用满减栈
② 栈帧串联构成栈
以函数为单位维护栈帧,栈帧串联构成栈
04. 代码重定位 & SDRAM初始化
与函数栈相关的ARM寄存器,
R11(fp):指向当前函数栈的栈底(类似EBP)
R13(sp):指向当前函数栈的栈顶
R10(sl):栈界限寄存器,持有当前函数栈的地址下限(对于满减栈)

特别说明:上图中的压栈内容为编译器不加任何优化时生成,目的是可以按统一的方式回溯栈(e.g. backtrace时)

B. 栈的作用

① 保存局部变量
C代码如下:
04. 代码重定位 & SDRAM初始化
反汇编如下:
04. 代码重定位 & SDRAM初始化
分析一下函数栈的演进图:
04. 代码重定位 & SDRAM初始化
a. 将fp压栈以保存原函数栈帧的栈底

b. 建立函数栈帧操作
push {fp}
add fp, sp, #0(即mov fp, sp)

c. 撤销函数栈操作
add sp, fp, #0(即mov sp, fp)
pop {fp}

d. 局部变量a在main函7206数栈帧中
e. 通过寄存器r0带回main函数返回值
f. 通过bx lr实现main函数返回

② 参数传递
C代码如下:
04. 代码重定位 & SDRAM初始化
反汇编如下:
04. 代码重定位 & SDRAM初始化
同样分析一下函数栈的演进过程~~
04. 代码重定位 & SDRAM初始化
a. 注意lr和fp的入栈位置
lr是R14,fp是R11,所以入栈后lr在高地址,fp在低地址

b. 此次main函数栈中保存了lr
这是因为main函数还要调用func1函数,所以将lr寄存器压栈。即非叶子函数在建立函数栈时,需要将lr入栈保存。

c. 超出r0 ~ r3的参数通过主调函数栈传递
示例中的5和6就通过main函数栈传递,而且func1在使用这2个参数时也是从main函数栈中取出,并没有复制一份到自己的栈中

d. 通过r0 ~ r3传递的参数会被压入被调函数栈
这也就是AAPCS中被调函数无需恢复r0 ~ r3的原因

栈演进

图.vsdx

32.44KB

③ 保存寄存器的值
C代码如下:
04. 代码重定位 & SDRAM初始化
反汇编如下:
04. 代码重定位 & SDRAM初始化

a. 由于func1中仍需要用r0 & r1传递参数,所以需要将r0 & r1的值保存到栈中

C. 如何初始化

① ARM各模式均有自己的sp寄存器,所以可以按模式初始化栈
操作系统也利用了这一特性,区分用户态和内核态使用的栈。初始化栈时,可以只涉及需要用到的模式。

初始化栈时,需要将sp指向一段可用的内存
所谓"可用",包含2点,
a. 该段内存已经初始化(或者这段内存无需初始化)
b. 该段内存不会用于其他目的

③ 在启动代码阶段设置SVC模式的栈
a. 系统启动时进入SVC模式,所以需要设置SVC模式的栈
当然,后续还要设置中断使用的栈

b. 在未初始化内存时只能将栈设置在iRAM中

c. iRAM中栈的位置可以参考三星官方手册
04. 代码重定位 & SDRAM初始化
由于使用满减栈,将sp_svc设置为0xD0037D80

3)初始化bss段

A. bss段的作用

① C程序中希望未初始化的全局变量 / 静态局部变量有确定的值
② 初始化bss段就是将bss段清零
③ 什么类型的变量存储在bss段?
未初始化或初始化为0的全局变量 / 静态局部变量存储在bss段
C代码如下:
04. 代码重定位 & SDRAM初始化
反汇编如下:
04. 代码重定位 & SDRAM初始化

注意:从上述反汇编可见静态局部变量的变量名在编译时会被修改~~

B. 如何初始化

① 需要借助链接器脚本中的2个变量:_bss_start & _bss_end
04. 代码重定位 & SDRAM初始化

② 在启动汇编代码中构造循环,将bss段清零
04. 代码重定位 & SDRAM初始化

特别注意:如果待初始化的bss段位于SDRAM,需要在内存初始化之后再进行bss段的初始化。

C. 深入理解BSS段

04. 代码重定位 & SDRAM初始化
要点:
① BSS段即Block Started by Symbol
② BSS段只是place holder,并不占用目标文件中的空间。可以理解为标识要占用的内存范围
③ 设置BSS段就是为了节省目标文件的存储空间

D. 专题:C程序如何使用链接器脚本变量

① 问题缘由
在类似uboot命令遍历等代码中,会将特定的数据链接到指定的程序段,目的是为了便于在C程序中集中访问这一区域
比如在x210 uboot在链接器脚本中定义了.u_boot_cmd段,之后将描述uboot命令的cmd_tbl_t结构全部链接到该段。之后就可以通过链接器脚本变量__u_boot_cmd_start & __u_boot_cmd_end遍历这一区域
04. 代码重定位 & SDRAM初始化

04. 代码重定位 & SDRAM初始化

② 链接器脚本定义
04. 代码重定位 & SDRAM初始化
在cc_bootloader的链接器脚本中定义.bss段,并定义变量bss_start & bss_end标识.bss段的起止地址
通过反汇编可知.bss段范围,因此bss_start的值应为0x2000c12cbss_end的值应为0x2000c930
04. 代码重定位 & SDRAM初始化

③ 汇编代码定义
为辅助验证,我们在start.S中定义_bss_start & _bss_end变量并存储链接器脚本变量
04. 代码重定位 & SDRAM初始化
我们同样通过反汇编验证这2个变量的状态
04. 代码重定位 & SDRAM初始化

此处我们可以总结出链接器脚本变量 & 汇编语言变量的不同之处,
a. 链接器脚本变量类似宏定义,变量本身就是作为一个符号常量使用,所以在初始化bss段时才有ldr r0, =bss_start命令
b. 汇编语言变量是符号地址,符号地址标识的内存中存储变量的值

此处就不得不讨论下C语言中的变量,以int a为例,C语言变量具有3个属性,
· 变量名:a
· 变量地址:通过&a获得
· 变量值:存储在&a地址对应的内存中,通过a获得。因此直接使用a,相当于对内存进行了一次寻址并解引用

④ C程序中验证
在C程序中声明bss_start(链接器脚本变量) & _bss_start(汇编语言变量)
04. 代码重定位 & SDRAM初始化

之后分别打印这2个变量的变量值和变量地址(说明:由于代码改动,.bss地址有变~~)
04. 代码重定位 & SDRAM初始化

04. 代码重定位 & SDRAM初始化

首先需要明确的是,我们要获得的是.bss段的起始地址(此处为0x20004f20)。此处可以明显看出C程序对链接器脚本变量 & 汇编语言变量的不同处理方式
a. 链接器脚本变量bss_start
bss_start变量的地址(&bss_start)才是.bss段的起始地址
即链接器变量的值是C语言变量的地址

b. 汇编语言变量_bss_start
由于汇编语言变量也有地址和值的概念,所以C程序中_bss_start变量的值就是.bss段的起始地址
即汇编语言变量的值是C语言变量的值

2. iCache操作

1)Cache的作用

04. 代码重定位 & SDRAM初始化
① Cache是一种容量小但存取速度非常快存储器,他保存最近用到的内存中数据的拷贝。
② Cache运作基于程序数据访问的局部性
对程序员来说,Cache是透明的。他自动决定保存哪些数据、覆盖哪些数据。我们能做的就是开关Cache功能。

2)Cache的分类

iCache:指令Cache,用于存放指令
dCache:数据Cache,用于存放数据

S5PV210 Cache情况如下:
04. 代码重定位 & SDRAM初始化
说明1:CPU内部的I/D Cache就构成了哈佛结构
说明2:裸机代码中一般不需要dCache,因为dCache需要开启MMU才能起作用
04. 代码重定位 & SDRAM初始化

3)开关iCache实例

① iCache的功能由CP15 c1的Control寄存器控制
04. 代码重定位 & SDRAM初始化

04. 代码重定位 & SDRAM初始化
根据0x00C50078的初始值,CPU复位时MMU/iCache/dCache均是关闭的。

② 是否使能iCache就是将该寄存器的bit[12]清0或置1
04. 代码重定位 & SDRAM初始化

04. 代码重定位 & SDRAM初始化

③ 根据S5PV210启动流程手册,在BL0中会打开iCache

④ 使用一段流水灯闪烁的程序进行验证,关闭iCache后,流水灯闪烁明显变慢;打开iCache后闪烁频率恢复。
可见iCache功能确已生效~~

3. 代码重定位与链接器脚本

1)链接地址 & 运行地址

A. 链接地址
① 含义:链接程序时指定的地址,一般为程序的预期运行地址
② 指定方式:
gcc编译选项(e.g. -Ttext=0x0)
链接器脚本(后文详述)

B. 运行地址
① 含义:程序实际被加载运行的内存地址

说明:链接地址与运行地址可以不匹配
以当前的裸机代码为例,链接地址为0x0,运行地址为0xd0020010。而在这段地址不匹配的运行期间,只能运行位置无关编码

2)位置无关编码 & 位置相关编码

位置无关编码(Position Independent Code,PIC):可执行程序在运行时与内存地址无关(e.g. bl)。
位置相关编码:可执行程序运行时与内存地址相关(e.g. ldr)。

说明1:结合上文,在运行地址与链接地址不匹配的情况下,只能使用位置无关编码(e.g. bl)。他们的本质是寻址时以PC为基准,不依赖链接地址。

说明2:位置无关编码有自身的限制,由于需要将以PC为基准的寻址偏移量编码进指令机器码中,所以寻址范围有限(e.g. bl指令的寻址范围只有±32MB)。而使用ldr伪指令进行寻址时,可达到4GB范围。

3)链接器脚本详解

A. 程序段的概念

编译生成的.o文件以段的方式组织,每个段有自己的名字,在链接过程中会以段名来组织相关段的链接。
段名分为两种,
① 编译&链接器内部预定义段名,比如
代码段(.text):函数编译后生成
数据段(.data):初始化为非零的全局变量 & 静态局部变量
bss段:即零初始化段(ZI,zero initial),对应未初始化或初始化为零的全局变量 & 静态局部变量

② 程序员自定义段名,比如
uboot中的.u_boot_cmd段,通过将所有uboot命令链接在同一个段中,简化对uboot命令的管理与使用。
04. 代码重定位 & SDRAM初始化

B. 链接器脚本简介

链接器的作用是把一个或多个输入文件合成一个输出文件,我们可以通过命令行或链接器脚本告诉链接器如何将多个输入文件合并成一个输出文件。
② 每个链接过程一般由链接脚本(linker script,常以lds作为文件后缀名)控制,链接脚本主要用于规定如何把输入文件内的section放入输出文件中,并控制输出文件内各部分在程序地址空间内的布局
③ 链接器有个默认的内置链接脚本,可以使用ld --verbose查看
04. 代码重定位 & SDRAM初始化

C. 脚本格式

a. 总则
① 链接脚本由一系列命令组成,每个命令由一个关键字(一般在其后紧跟相关参数)或一条对符号的赋值语句组成,命令由分号;分隔开
② 文件名或格式名中如果包含分号或其他分隔符,则要用引号将名字全称引用起来。
③ /**/ 之间的是注释
b. 简单示例说明(重点在SECTIONS 命令格式)
04. 代码重定位 & SDRAM初始化
上述链接脚本作用:将输出文件的text section 定位在0x10000,data section 定位在0x80000000
解析:
SECTIONS 命令告诉ld 如何把输入文件的sections 映射到输出文件的各个section中以及如何把输出section 放入程序地址空间(VMA)和进程地址空间(LMA)
SECTIONS 命令格式一般如下:
SECTIONS
{
SECTIONS-COMMAND
SECTIONS-COMMAND
......
}
SECTIONS-COMMAND有四种
A. ENTRY命令
B. 符号赋值语句
C. 一个输出section的描述(output section description)
D. 一个section 的叠加描述(overlay description)
上述示例链接脚本中的SECTIONS-COMMAND有:
. = 0x1000;
/*符号赋值语句,把定位器符号设置为0x10000,若不指定,则该符号初始值为0*/
特别说明:. 是一个特殊符号,他是定位器,一个位置指针,他指向程序地址空间内的某位置,该符号只能在SECTIONS命令内使用。
.text : {*(.text)}
/*输出section描述*代表任意输入文件,所以是将所有输入文件的.text段合并成一个.text段,该段地址由定位器符号的值指定,此处为0x10000*/
. = 0x80000000;
/*符号赋值语句,把定位器符号设置为0x80000000*/
.data : {*(.data)}
/*输出section描述,将所有输入文件的.data段合并成一个.data段,该段地址为0x80000000*/
.bss : {*(.bss)}
/*输出section描述,将所有输入文件的.bss段合并成一个.bss段,该段地址为0x80000000 + .data 段大小*/
注:也就是说,链接器每读完一个section描述后,将定位器符号的值增加该section的大小。
说明1:我们把输入文件内的section称为输入section(input section),把输出文件内的section称为输出section(output section)
说明2:如果整个链接器脚本内没有SECTIONS 命令,那么链接器将所有同名输入section合成为一个输出section,各输入section的顺序为他们被链接器发现的顺序。
说明3:如果某个输入section没有在SECTIONS 命令中提到,那么该section将被直接拷贝成输出section
说明4:目标文件的每个section至少包含两个信息:名字和大小。但大部分section还包含与他相关联的一块数据,称为section contents(section 内容)。
说明5:一个section可被标记为:loadable(可加载的)allocatable(可分配的)
loadable section:在输出文件运行时,相应的section内容将被载入进程地址空间中
allocatable section:内容为空的section可被标记为可分配的,在输出文件运行时,在进程地址空间中空出大小同section指定大小的部分,某些情况下,这块内存必须被置零。
如果一个section不是可加载的或可分配的,那么该section通常包含了调试信息
每个可加载的或可分配的输出section通常包含两个地址VMA(Virtual Memory Address,虚拟内存地址或程序地址空间地址)和LMA(Load Memory Address,加载内存地址或进程地址空间地址),通常这两个地址相同。

c. 简单脚本命令

① ENTRY(SYMBOL)
将符号SYMBOL的值设置成入口地址,arm-linux-ld 有多种方法设置进程入口地址,按优先级排序如下:
使用arm-linux-ld 的-e 选项
链接脚本中的ENTRY(SYMBOL)命令
如果定义了start 符号,使用start 符号值
如果存在.text section,使用.text section 的第一字节位置值
使用值0

uboot示例:
04. 代码重定位 & SDRAM初始化
② INCLUDE filename
包含其他链接脚本
③ OUTPUT(FILENAME)
定义输出文件的名字,其优先级低于arm-linux-ld 的-o 选项,所OUTPUT(FILENAME)一般用来定义默认的输出文件名,比如a.out

OK210 uboot链接器脚本:

u-boo

t.lds

1.81KB

4)为什么需要代码重定位

A. S5PV210启动流程回顾

04. 代码重定位 & SDRAM初始化
上图为三星官方推荐的启动方式,bootloader必须小于96KB且均在iRAM运行。主体步骤如下,
BL1由BL0根据不同启动设备自动读取
BL1初始化Flash以读取BL2(S5PV210的BL0中提供了块设备拷贝函数)
BL2初始化内存,并加载Linux kernel至内存运行

说明:如果采用上述启动方式,可以将bootloader的链接地址设置为iRAM地址

B. 什么是代码重定位

将代码拷贝到链接地址处运行(使得运行地址和链接地址一致

C. uboot采用的启动流程

① uboot需要重定位的原因
uboot大小一般均超过96KB的限制,必须在内存中运行

② uboot实际启动流程
BL1由BL0根据不同启动设备自动读取
BL1(即uboot的前16KB)初始化Flash和SDRAM,然后将整个uboot从Flash拷贝到DDR中(同样的,此处也可以使用BL0提供的块设备拷贝函数,进而可以避免过早地初始化Flash)
最后用一句长跳转指令跳转到DDR中继续运行(从iRAM跳转到DDR只能使用地址相关的长跳转,因为bl的范围有限)

说明1:这种启动方式使得uboot可以没有96KB的限制
说明2:由于uboot的主体是在SDRAM中运行,所以uboot的链接地址一般为SDRAM地址。所以在uboot跳转到SDRAM运行之前,只能使用地址无关编码。因为此时运行地址(iRAM中)和链接地址(SDRAM中)不匹配
说明3:将uboot从flash拷贝到SDRAM之前,需要初始化flash和SDRAM,所以对uboot的链接地址有要求,需要将相关的初始化代码链接在uboot的前16KB
04. 代码重定位 & SDRAM初始化

说明4:uboot不使用代码重定位的解决方案
可以用分散加载的方式解决:就uboot而言,就是将uboot分成2部分,2部分分别指定不同的链接地址。启动时将两部分加载到不同地址,BL1加载到iRAM,uboot加载到SDRAM。
本质仍然是将运行地址和链接地址匹配

5)如何实现代码重定位

任务:将iRAM中的代码从0xd0020010重定位到0xd0024000(练习性质,无需初始化SDRAM
04. 代码重定位 & SDRAM初始化
本质:将裸机代码从运行地址重定位到链接地址

思路:
① 通过链接器脚本将代码链接到0xd0024000
② dnw下载时将bin文件下载到0xd0020010(CPU规定的运行地址,前文有实验论证)
说明:前2点造成链接地址与运行地址不同,因此需要重定位

③ 代码执行时通过前段的少量位置无关代码将整个代码拷贝到0xd0024000
④ 使用长跳转,跳转到0xd0024000处继续执行

代码分析:
① adr和ldr伪指令
04. 代码重定位 & SDRAM初始化
adr指令加载符号地址时加载的是运行时地址;ldr指令加载的符号地址是链接地址。具体实现可以分析对应的汇编代码。
04. 代码重定位 & SDRAM初始化
目的:比较运行时地址和链接地址是否相等,以判断是否需要重定位。如果代码已经在链接地址运行,则无需重定位。

② 代码重定位流程
04. 代码重定位 & SDRAM初始化
r0:_start标号的运行地址,重定位的源地址
r1:_start标号的链接地址,重定位的目的地址
r2:bss段起始地址(链接器脚本变量),链接地址的拷贝终点
04. 代码重定位 & SDRAM初始化
因此,实际的代码拷贝长度为bss_start - _start

注意1:bss段是不需要重定位的,后续的clean_bss过程会将该段内存清空。如果将运行地址处的bss段拷贝到链接地址处,没有实际意义~~
注意2:清bss段应该在内存初始化之后,此处均使用iRAM,所以没有突出该问题。

示例代码:

star

t.S

2KB


6)代码重定位对数据段的影响

分析:根据上文分析,在代码重定位之前,不能进行地址相关操作。而C语言中对全局变量的使用也应属于地址相关操作,因为对全局变量的访问是通过链接地址进行的。
04. 代码重定位 & SDRAM初始化

04. 代码重定位 & SDRAM初始化

04. 代码重定位 & SDRAM初始化

验证:
reset标号处,
04. 代码重定位 & SDRAM初始化
对内存进行初始化,但是不进行代码重定位,同时在iRAM中而不是在DDR中运行start_main

start_main函数处,
04. 代码重定位 & SDRAM初始化
设置全局变量data_global和bss_global,根据是否初始化,data_global被链接到.data段,bss_global被链接到.bss段,然后打印这2个全局变量的值。此处使用putc而不是printf,是因为printf会调用大量全局变量资源,也就是有大量地址相关操作,而此时并未进行代码重定位。

连续执行2次,发现.data段中的全局变量输出类似乱码,.bss段中全局变量输出正常。这是符合预期的,因为我们调用了clean_bss过程
04. 代码重定位 & SDRAM初始化

下面进行一些修改,增加调用代码重定位过程,但是仍然在iRAM中运行
04. 代码重定位 & SDRAM初始化

结果.data段和.bss段中的全局变量均能正确打印
04. 代码重定位 & SDRAM初始化
这也就印证了之前的分析,即C语言中全局变量的使用有赖于代码重定位,对全局变量的访问也是一种地址相关操作

注意:.data段中有确确实实的数据,而且是二进制文件的一部分;.bss段只是记录地址的句柄,具体数据并不在二进制文件中,需要启动代码将该段清零

4. 内存初始化

1)内存的分类

04. 代码重定位 & SDRAM初始化

 
SRAM
DRAM
构成原件
晶体管锁存器
小电容
优点
速度快
集成度高/价格低
缺点
集成度低/价格高
速度慢/需要定期刷新
使用场景
存储容量不高但需要存取速度快(Cache/Stepping Stone)
存储容量高但存取速度相对较低

目前主流开发板使用的DDR内存均基于SDRAM内存改进而来。
SDRAM(Synchronous Dynamic Random Access Memory):同步动态随机存储器
同步:内存工作需要同步时钟,内部命令的发送以及数据的传输都以该时钟为基准
动态:存储阵列需要不断刷新来保证数据不丢失
随机:数据访问不需要依次进行,而是可以*指定地址进行读写

DDR则是Double Data Rate SDRAM,利用同步时钟的双边沿传输数据,使得传输数据翻倍。

补充:LPDDR为低功耗(low power)DDR

2)内存的结构

A. 表结构(L-Bank)

04. 代码重定位 & SDRAM初始化
内存内部为二维表结构,访问具体存储单元时,需要指定行地址(row address)和列地址(column address)
而一张二维表就是一个Logic Bank(L-Bank)

B. 内存内部寻址

04. 代码重定位 & SDRAM初始化
由于技术和成本的原因,一块内存不可能将所有的存储单元都集成到一个L-Bank中,而是做成多个L-Bank。
因此可以得到内存中一个存储单元的寻址方式
L-Bank片选 + 行地址 + 列地址

C. 内存容量推导

内存位宽:即内存中每个存储单元的比特数,也是每次内存操作可以读写的比特数(也叫IO数)。常见的有4/8/16/32 bit

内存容量(bit) = L-Bank数 * L-Bank容量
= L-Bank数 * (行地址 * 列地址 * IO数)

以OK210/x210使用的K4T1G164QE内存为例,容量描述为:8Mbit * 16 I/Os * 8banks,所以每片内存为128MB

D. 内存芯片手册分析

说明:x210开发板核心板原理图中使用的内存型号为K4T1G164QE(三星),但实际贴片时使用的是NT5TU128M8GE(南亚),但二者完全兼容。

① 芯片代号分析
04. 代码重定位 & SDRAM初始化

重点信息如下,
Density & Refresh(1G):1Gb,8K/64ms
Organization(16):*16
Bank(4):8 Banks
Interface(Q):SSTL_18(1.8V,1.8V)

② 芯片地址线分析
04. 代码重定位 & SDRAM初始化
Bank Address:BA0 ~ BA2,共3条地址线,对应8个L-Bank

Row Address:A0 ~ A12,共13条
Column Address:A0 ~ A9,共10条
2^13 * 2^10 = 2^23 = 8M,对应每个L-Bank有8M个存储单元

特别说明:x210开发板实际使用的内存型号
04. 代码重定位 & SDRAM初始化
根据后续实际内存初始化代码,x210开发板使用了上述16Mbit * 8 I/Os * 8banks的内存,所以地址线如下,
Bank Address:BA0 ~ BA2,共3条地址线,对应8个L-Bank
Row Address:A0 ~ A13,共14条
Column Address:A0 ~ A9,共10条

3)内存的硬件连接

A. S5PV210内存地址空间

04. 代码重定位 & SDRAM初始化
S5PV210有2个内存Bank,
DRAM0:512MB,物理地址范围0x20000000 ~ 0x3FFFFFFF
DRAM1:1GB,物理地址范围0x40000000 ~ 0x7FFFFFFF

B. x210核心板内存连接

04. 代码重定位 & SDRAM初始化
x210共使用4片8Mbit * 16 I/Os * 8banks内存,分别连接在2个内存Bank上。下面先分析一下DMC0(对应引脚Xm1)上的内存连接情况,DMC1(对应引脚Xm2)与其一致。
04. 代码重定位 & SDRAM初始化
分析:
① 2片内存地址线(BA + 行地址 + 列地址)相同;数据线分别接低16位和高16位,通过并联构成32位数据操作宽度。
2片内存共256MB,所以DMC0的合法物理地址为:0x20000000 ~ 0x2fffffff;DMC1的合法物理地址为:0x40000000 ~ 0x4fffffff

注意:x210开发板实际贴片于此不同,但原理一致

4)内存初始化代码分析

A. 内存初始化概述

① 所谓内存初始化就是设置SoC内部的DRAM controller(简称DMC)

② DMC的主要功能有2点,
a. 地址信号转换
程序中的物理地址 --> 地址线上的信号 --> 内存内部的寻址信号
b. 处理内存访问的各种时序(由于时序复杂,所以优先使用内存原厂代码)

③ 根据DMC的功能,便引出了DMC初始化的主要内容,
a. 向DMC描述物理地址空间(e.g. 合法的物理地址范围)
b. 向DMC描述内存内部寻址信号(bank address/row address/column address)
c. 向DMC描述内存操作时序

④ DMC初始化主体步骤
a. PHY DLL初始化(DDR PLL初始化)
b. 设置DMC寄存器
c. 通过DMC发送命令初始化内存

B. 内存初始化核心代码说明

说明:DMC初始化共27个步骤,下面根据先后顺序对核心寄存器的设置进行说明,此处略过有关内存操作时序的设置。并以DMC0的初始化为例,DMC1初始化与其一致。

a. 设置S5PV210与DDR连接引脚的驱动能力
目的:给内存提供稳定的电压
04. 代码重定位 & SDRAM初始化
与存储设备连接的IO管脚为MPx系列管脚,其中DRAM使用MP1_x和MP2_x管脚。
04. 代码重定位 & SDRAM初始化
代码中将MP1_x和MP2_x管脚的驱动能力设置为2x

b. DRAM port时钟设置
step 2 ~ 4用于设置时钟,主要是开启DRAM PLL然后锁存
a. 在DLL关闭的情况下设置DLL参数
b. 置位DLL On
c. 置位DLL start
d. 等待DLL锁存成功

=================================================================
说明:开始向DMC描述使用的内存信息
c. 设置DMC_CONCONTROL
在DMC0设置完成前,先关闭auto refresh
04. 代码重定位 & SDRAM初始化

d. 设置DMC0_MEMCONTROL
04. 代码重定位 & SDRAM初始化
说明:在DMC0处并联了2片16位IO的内存芯片(实际贴片为并联4片8位IO的内存芯片),最终作为1片32位IO的内存芯片由DMC0控制

e. 设置DMC0_MEMCONFIG_0
DMC0中memory chip0的参数设置寄存器
04. 代码重定位 & SDRAM初始化

说明1:Base Address & Offset Address
DMC0连接256MB内存,物理地址范围为:0x20000000 ~ 0x2fffffff。所以Offset Address范围为0x0000000 ~ 0x0fffffff。
所以AXI Base Address字段设置为0x20,AXI Base Address Mask字段设置为0xF0

说明2:Bank数/行地址数/列地址数/地址模式,可参考内存数据手册

f. 设置DMC0_MEMCONFIG_1
DMC0中memory chip1的参数设置寄存器
如果有2片chip,才需要设置MEMCONFIG_1。但我们DMC0只使用了1片内存(并联构成),代码中填充默认值。

g. 设置内存的一系列时序

h. 通过DMC_DIRECTCMD寄存器发送一系列命令
S5PV210通过向这个寄存器写值实现对DDR芯片的控制

i. 设置DMC_CONCONTROL
配置完成,使能auto refresh

参考资料(确认可正确运行的x210内存初始化代码):

s5pv21

0.h

27.84KB

sdram_ini

t.S

12.5KB