ARM 的 SDRAM和重定位

 

这里记录的是 ARM 裸机从启动到 SDRAM 初始化的 BL1 内部过程

这里记录的是 ARM 裸机从启动到 SDRAM 初始化的 BL1 内部过程

汇编写启动代码之关看门狗

什么是看门狗?

看门狗(watch dog timer 看门狗定时器)。现实中因为一些外部因素,电子设备经常会跑飞或者死机(譬如极端炎热、极端寒冷、工业复杂场合)。在这种情况下我们希望设备自动复位而不需要人工干预(无人值守)。看门狗用来完成这个工作。

看门狗其实是我们SoC内部的一个定时器(类似于闹钟,类似于门口的狗),定好时间之后看门狗定时器会去计时,时间到之前(狗饿了之前)必须去重新置位看门狗定时器(喂狗),如果没有喂狗则系统会被强制复位。系统在正常工作时,系统软件会自己去喂狗,所以看门狗定时器不会复位。但是系统一旦故障跑飞啥的,看门狗就没人喂了,然后下一个周期就会自动复位,达到我们期望的效果。

分析硬件物理特性、原理图、数据手册

物理特性上看门狗其实是个定时器(跟现实中的闹钟类似),硬件上就是SoC内部的一个内部外设。

原理图:看门狗不用分析原理图,因为看门狗属于内部外设,且没有外部相关的原件与他有关,所以不需要原理图分析,原理图上根本找不到和看门狗有关的地方。

数据手册:在数据手册的Section7.3

The Watchdog Timer (WDT) in S5PV210 is a timing device that resumes the controller operation after malfunctioning due to noise and system errors. WDT can be used as a normal 16-bit interval timer to request interrupt service. The WDT generates the reset signal.

看门狗寄存器

寄存器 地址 R/W 描述 初始值
WTCON   R/W 控制寄存器 0x00008021
WTDAT 0xE270_0004 R/W 数据寄存器 0x00008000
WTCNT 0xE270_0008 R/W 计数寄存器 0x00008000
WTCLRINT 0xE270_000C W 中断清除寄存器  

控制寄存器中:

WTCON Bit 描述 初始值
Watchdog timer [5] 使能和关闭看门狗寄存器 1

所以当 WTCON[5] = 0 时,就可以关闭看门狗定时器

#define WTCON 0xE2700000
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]

汇编写启动代码之设置栈和调用C语言

C语言运行时需要和栈的意义

“C语言运行时(runtime)”需要一定的条件,这些条件由汇编来提供。C语言运行时主要是需要栈

C语言与栈的关系:C语言中的局部变量都是用栈来实现的。如果我们汇编部分没有给C部分预先设置合理合法的栈地址,那么C代码中定义的局部变量就会落空,整个程序就死掉了。

我们平时在编写单片机程序(譬如51单片机)或者编写应用程序时并没有去设置栈,但是C程序还是可以运行的。原因是:在单片机中由硬件初始化时提供了一个默认可用的栈,在应用程序中我们编写的C程序其实并不是全部,编译器(gcc)在链接的时候会帮我们自动添加一个头,这个头就是一段引导我们的C程序能够执行的一段汇编实现的代码,这个代码中就帮我们的C程序设置了栈及其他的运行时需要。

CPU模式和各种模式下的栈

在ARM中37个寄存器中,每种模式下都有自己的独立的SP栈寄存器(r13),为什么这么设计?

如果各种模式都使用同一个SP,那么就意味着整个程序(操作系统内核程序、用户自己编写的应用程序)都是用一个栈的。你的应用程序如果一旦出错(譬如栈溢出),就会连累操作系统的栈也损坏,整个操作系统的程序就会崩溃。这样的操作系统设计是非常脆弱的,不合理的。

解决方案就是各种模式下用不同的栈。我的操作系统内核使用自己的栈,每个应用程序也使用自己独立的栈,这样各是各的,一个损坏不会连累其他人。

我们现在要设置栈,不可能而且也没有必要去设置所有的栈,我们先要找到自己的模式,然后设置自己的模式下的栈到合理合法的位置,即可。

注意:系统在复位后默认是进入SVC模式的。我们如何访问SVC模式下的SP呢?很简单,先把模式设置为SVC,再直接操作SP。但是因为我们复位后就已经是SVC模式了,所以直接设置SP即可。

查阅文档并设置栈指针至合法位置

栈必须是当前一段可用的内存(可用的意思是这个地方必须有被初始化过可以访问的内存,而且这个内存只会被我们用作栈,不会被其他程序征用)

当前CPU刚复位(刚启动),外部的DRRAM尚未初始化,目前可用的内存只有内部的SRAM(因为它不需初始化即可使用)。因此我们只能在SRAM中找一段内存来作为SVC的栈。

栈有四种:满减栈、满增栈、空减栈、空增栈。满指的是 SP 指向的地址是有效数据(不可覆盖),空指的是 SP 指向的地址是空的,没有数据,可以直接写入。

  • 满栈:进栈:先移动指针再存; 出栈:先出数据再移动指针
  • 空栈:xxx
  • 减栈:进栈:指针向下移动; 出栈:指针向上移动
  • 增栈:xxx

在ARM中,ATPCS(ARM关于程序应该怎么实现的一个规范)要求使用满减栈,所以不出意外都是用满减栈。结合《iROM_application_note》中的memory map,可知SVC栈应该设置为 0xd0037D80

irom

ATPCS即ARM-THUMB procedure call standard(ARM-Thumb过程调用标准)的简称。

PCS规定了应用程序的函数可以如何分开地写,分开地编译,最后将它们连接在一起,所以它实际上定义了一套有关过程(函数)调用者与被调用者之间的协议。

PCS强制实现如下约定:调用函数如何传递参数(即压栈方法,以何种方式存放参数),被调用函数如何获取参数,以何种方式传递函数返回值。

汇编代码

#define SVC_STACK	0xd0037d80
ldr sp, =SVC_STACK
//之后就可以调用 C 程序了
bl cfunction

在 C 中访问寄存器的方法:

#define GPJ0CON		0xE0200240
#define GPJ0DAT		0xE0200244
#define rGPJ0CON	*((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT	*((volatile unsigned int *)GPJ0DAT)
rGPJ0CON = 0x11111111;

volatile的作用是让程序在编译时,编译器不对程序做优化。优化有时候是ok的,但是有时候是自作聪明会造成程序不对。如果你的一个变量是易变的,不希望编译器帮我们做优化,就在这个变量定义时加volatile。

在编译时会出现 undefined reference to __aeabi_unwind_cpp_pr1 的错误,添加 -nostdlib 这个编译选项即可解决。nostdlib 就是不使用标准函数库。标准函数库就是编译器中自带的函数库,用 -nostdlib 可以让编译器链接器优先选择我程序内自己写的函数库

汇编写启动代码之开iCache

什么是cache

cache是一种内存,叫高速缓存。从容量来说:CPU < 寄存器 < cache < DDR。从速度来说:CPU > 寄存器 > cache > DDR

cache的存在,是因为寄存器和 ddr 之间速度差异太大,ddr 的速度远不能满足寄存器的需要(不能满足 cpu 的需要,所以没有 cache 会拉低整个系统的整体速度)

整个系统中CPU的供应链由:寄存器+cache+DDR+硬盘/flash四阶组成,这是综合考虑了性能、成本后得到的妥协的结果。210内部有 32KB icache 和 32kb dcache。icache 是用来缓存指令的;dcache 是用来缓存数据的。

cache的意义:指令平时是放在硬盘/flash中的,运行时读取到DDR中,再从DDR中读给寄存器,再由寄存器送给cpu。但是DDR的速度和寄存器(代表的就是CPU)相差太大,如果CPU运行完一句再去DDR读取下一句,那么CPU的速度完全就被DDR给拖慢了。解决方案就是icache。

icache工作时,会把我们CPU正在运行的指令的旁边几句指令事先给读取到icache中(CPU设计有一个基本原理:代码执行时,下一句执行当前一句代码旁边代码的可能性要大很多)。当下一句CPU要指令时,cache首先检查自己事先准备的缓存指令中有没这句,如果有就直接拿给CPU,如果没有则需要从DDR中重新去读取拿给CPU,并同时做一系列的动作:清缓存、重新缓存。

iRom 的 BL0 自动打开了 iCache,而 iCache 的操作是由协处理器控制的。

	mrc p15,0,r0,c1,c0,0;			// 读出cp15的c1到r0中
	bic r0, r0, #(1<<12)			// bit12 置0  关icache
	orr r0, r0, #(1<<12)			// bit12 置1  开icache
	mcr p15,0,r0,c1,c0,0;

重定位引入和链接脚本

为什么要重定位

一个事实:大部分指令是位置有关编码。原因:链接地址和运行地址有时候必须不相同,而且还不能全部用位置无关码,这时候只能重定位。

  • 位置无关编码(PIC,position independent code):

    汇编源文件被编码成二进制可执行程序时编码方式与位置(内存地址)无关。

  • 位置有关编码

    汇编源码编码成二进制可执行程序后和内存地址是有关的。

我们在设计一个程序时,会给这个程序指定一个运行地址(链接地址)。就是说我们在编译程序时其实心里是知道程序将来运行时所在的内存地址,而且必须给编译器链接器指定这个地址(链接地址)才行。最后得到的二进制程序理论上是和你指定的运行地址有关的,将来这个程序被执行时必须放在当时编译链接时给定的那个地址(链接地址)下才行,否则不能运行(就叫位置有关代码)。但是有个别特别的指令他可以跟指定的地址(链接地址)没有关系,也就是说这些代码实际运行时不管放在哪里都能正常运行。

对比:位置无关代码要好一些,适应性强,放在哪里都能正常运行;位置有关代码就必须运行在链接时指定的地址上,适应性差。位置无关码有一些限制,不能完成所有功能,有时候不得不使用位置有关代码。

链接地址和运行地址

  • 分清楚这两个概念:

    • 链接地址:

      链接时指定的地址(指定方式为:Makefile中用-Ttext,或者链接脚本)

    • 运行地址

      程序实际运行时地址(指定方式:由实际运行时被加载到内存的哪个位置说了算)

对于位置有关代码来说:最终执行时的运行地址和编译链接时给定的链接地址必须相同,否则一定出错。Makefile 中用 -Ttext 0x0 来指定链接地址是 0x0。这意味着我们认为这个程序将来会放在 0x0 这个内存地址去运行。

但是实际上我们运行时的地址是 0xd0020010 (我们用 dnw 下载时指定的下载地址)。这两个地址看似不同,但是实际相同。这是因为 S5PV210 内部做了映射,把SRAM映射到了 0x0 地址去。

再解S5PV210的启动过程

三星推荐的启动方式中

bootloader必须小于96KB并大于16KB。假定bootloader为80KB,启动过程是这样子:

  • 先开机上电后BL0运行,BL0会加载外部启动设备中的bootloader的前16KB(BL1)到SRAM中去运行,
  • BL1运行时会加载BL2(bootloader中80-16=64KB)到SRAM中(从SRAM的16KB处开始用)去运行;
  • BL2运行时会初始化DDR并且将OS搬运到DDR去执行OS,启动完成。

uboot实际使用的方式

uboot大小随意,假定为200KB。启动过程是这样子:

  • 先开机上电后BL0运行,BL0会加载外部启动设备中的uboot的前16KB(BL1)到SRAM中去运行,
  • BL1运行时会初始化DDR,然后将整个uboot搬运到DDR中
  • 然后用一句长跳转(从SRAM跳转到DDR)指令从SRAM中直接跳转到DDR中继续执行uboot直到uboot完全启动。
  • uboot启动后在uboot命令行中去启动OS。

运行时地址由什么决定?

运行时的地址是由程序在内存中运行时决定的(编译链接时是无法绝对确定运行时地址的)

链接地址由什么决定

链接地址是由程序员在编译链接的过程中,通过 Makefile 中 -Ttext xxx 或者在链接脚本中指定的。程序员事先会预知自己的程序的执行要求,并且有一个期望的执行地址,并且会用这个地址来做链接地址。

举例:

  • linux中的应用程序。

    gcc hello.c -o hello,这时使用默认的链接地址就是 0x0,所以应用程序都是链接在0地址的。因为应用程序运行在操作系统的一个进程中,在这个进程中这个应用程序独享4G的虚拟地址空间。所以应用程序都可以链接到0地址,因为每个进程都是从0地址开始的。(编译时可以不给定链接地址而都使用0)

  • 210中的裸机程序。

    运行地址由我们下载时确定,下载时下载到 0xd0020010,所以就从这里开始运行。(这个下载地址也不是我们随意定的,是 iROM 中的 BL0 加载 BL1 时事先指定好的地址,这是由 CPU 的设计决定的)。所以理论上我们编译链接时应该将地址指定到 0xd0020010,但是实际上我们在之前裸机程序中都是使用位置无关码PIC,所以链接地址可以是0。

从源码到可执行程序的步骤

  • 预编译:
    • 预编译器执行。譬如C中的宏定义就是由预编译器处理,注释等也是由预编译器处理的。
  • 编译:
    • 编译器来执行。把源码.c .S编程机器码.o文件。
  • 链接:
    • 链接器来执行。把.o文件中的各函数(段)按照一定规则(链接脚本来指定)累积在一起, 形成可执行文件。
    • strip: strip是把可执行程序中的符号信息给拿掉,以节省空间。(Debug版本和Release版本)
    • objcopy:由可执行程序生成可烧录的镜像bin文件。

程序段的概念

段就是程序的一部分,我们把整个程序的所有东西分成了一个一个的段,给每个段起个名字,然后在链接时就可以用这个名字来指示这些段。也就是说给段命名就是为了在链接脚本中用段名来让段站在核实的位置。

段名分为2种:

  • 一种是编译器链接器内部定好的,先天性的名字;
  • 一种是程序员自己指定的、自定义的段名。

先天性段名:

  • 代码段:(.text),又叫文本段,代码段其实就是函数编译后生成的东西
  • 数据段:(.data),数据段就是C语言中有显式初始化为非0的全局变量
  • bss段:(.bss),又叫ZI(zero initial)段,就是零初始化段,对应C语言中初始化为0的全局变量。

分析一些问题,跟这里结合,然后试图明白一些本质:

  • C语言中全局变量如果未显式初始化,值是0。本质就是C语言把这类全局变量放在了 bss 段,从而保证了为0
  • C运行时环境如何保证显式初始化为非0全局变量的值在main之前就被赋值了?就是因为它把这类变量放在了 .data段中,而 .data 段会在 main 执行之前被处理(初始化)。

链接脚本究竟要做什么

链接脚本其实是个规则文件,他是程序员用来指挥链接器工作的。链接器会参考链接脚本,并且使用其中规定的规则来处理.o文件中那些段,将其链接成一个可执行程序。

链接脚本的关键内容有2部分:段名 + 地址(作为链接地址的内存地址)

链接脚本的理解:

  • SECTIONS {}这个是整个链接脚本
  • . 点号在链接脚本中代表当前位置。
  • = 等号代表赋值
SECTIONS
{
	. = 0xd0024000;  # 链接地址
	
	.text : { # 数据段
		start.o
		* (.text) # 所有的 text 段
	}
    		
	.data : {
		* (.data)
	}
	
	bss_start = .;  # 之后的C语言和汇编可以引用
	.bss : {
		* (.bss)
	}
	
	bss_end  = .;	
}

代码重定位实战

任务:在SRAM中将代码从 0xd0020010 重定位到 0xd0024000。任务解释:本来代码是运行在 0xd0020010的,但是因为一些原因我们又希望代码实际是在 0xd0024000 位置运行的。这时候就需要重定位了。

思路

  • 通过链接脚本将代码链接到 0xd0024000
  • dnw 下载时将 bin 文件下载到 0xd0020010
  • 代码执行时通过代码前段的少量位置无关码将整个代码搬移到 0xd0024000
  • 使用一个长跳转跳转到 0xd0024000 处的代码继续执行,重定位完成

第一点加上第二点,就保证了:代码实际下载运行在 0xd0020010,但是却被链接在 0xd0024000。从而为重定位奠定了基础。

当我们把代码链接地址设置为 0xd0024000 时,实际隐含意思就是我这个代码将来必须放在 0xd0024000 位置才能正确执行。如果实际运行地址不是这个地址就要出事(除非代码是PIC位置无关码),当以上都明白了后,就知道重定位代码的作用就是:在PIC执行完之前(在代码中第一句位置有关码执行之前)必须将整个代码搬移到0xd0024000 位置去执行,这就是重定位。

长跳转

首先这句代码是一句跳转指令(ARM中的跳转指令就是类似于分支指令B、BL等作用的指令),跳转指令通过给PC(r15)赋一个新值来完成代码段的跳转执行。长跳转指的是跳转到的地址和当前地址差异比较大,跳转的范围比较宽广。

当我们执行完代码重定位后,实际上在SRAM中有2份代码的镜像(一份是我们下载到 0xd0020010 处开头的,另一份是重定位代码复制到 0xd0024000 处开头的),这两份内容完全相同,仅仅地址不同。重定位之后使用 ldr pc, =led_blink 这句长跳转直接从 0xd0020010 处代码跳转到 0xd0024000 开头的那一份代码的 led_blink 函数处去执行。(实际上此时在SRAM中有2个 led_blink 函数镜像,两个都能执行,如果短跳转 bl led_blink 则执行的就是 0xd0020010 开头的这一份,如果长跳转 ldr pc, =led_blink 则执行的是0xd0024000 开头处的这一份)。这就是短跳转和长跳转的区别。

当链接地址和运行地址相同时,短跳转和长跳转实际效果是一样的;但是当链接地址不等于运行地址时,短跳转和长跳转就有差异了。这时候短跳转实际执行的是运行地址处的那一份,而长跳转执行的是链接地址处那一份。

总结:重定位实际就是在运行地址处执行一段位置无关码PIC,让这段PIC(也就是重定位代码)从运行地址处把整个程序镜像拷贝一份到链接地址处,完了之后使用一句长跳转指令从运行地址处直接跳转到链接地址处去执行同一个函数(led_blink),这样就实现了重定位之后的无缝连接。

adr与ldr伪指令的区别

ldr 和 adr 都是伪指令,区别是ldr是长加载、adr是短加载。

重点:adr指令加载符号地址,加载的是运行时地址;ldr指令在加载符号地址时,加载的是链接地址。

在汇编语言中, ldr 使用的是将一个数值赋值给 PC 寄存器,所以可以实现长跳转;而 adr 是通过 pc -= 5 的方式进行的跳转,所以只能短跳转。

重定位

重定位就是汇编代码中的 copy_loop 函数,代码的作用是使用循环结构来逐句复制代码到链接地址。

复制的源地址是SRAM的0xd0020010,复制目标地址是SRAM的0xd0024000,复制长度是bss_start减去_start。所以复制的长度就是整个重定位需要重定位的长度,也就是整个程序中代码段+数据段的长度。 bss段(bss段中就是0初始化的全局变量)不需要重定位。

清bss段

清除bss段是为了满足C语言的运行时要求(C语言要求显式初始化为0的全局变量,或者未显式初始化的全局变量的值为0,实际上C语言编译器就是通过清bss段来实现C语言的这个特性的)。

一般情况下我们的程序是不需要负责清零bss段的(C语言编译器和链接器会帮我们的程序自动添加一段头程序,这段程序会在我们的main函数之前运行,这段代码就负责清除bss)。但是在我们代码重定位了之后,因为编译器帮我们附加的代码只是帮我们清除了运行地址那一份代码中的bss,而未清除重定位地址处开头的那一份代码的bss,所以重定位之后需要自己去清除bss。

长跳转

清理完bss段后重定位就结束了。然后当前的状况是:

  • 当前运行地址还在 0xd0020010 开头的(重定位前的)那一份代码中运行着。
  • 此时SRAM中已经有了2份代码,1份在 d0020010 开头,另一份在 d0024000 开头的位置。
  • 然后就要长跳转了

汇编代码实践

#define WTCON		0xE2700000
#define SVC_STACK	0xd0037d80

.global _start
_start:
	// 关闭看门狗
	ldr r0, =WTCON
	ldr r1, =0x0
	str r1, [r0]
	
	// 设置 SVC 栈
	ldr sp, =SVC_STACK
	// 开 icache
	
	// 重定位
	adr r0, _start   // 加载运行时的_start地址
	ldr r1, =_start  // 加载编译时的链接地址
	ldr r2, =bss_start
	cmp r0, r1
	beq clean_bss
	
copy_loop:
	ldr r3, [r0], #4  // 读[r0]内容,并 r0+=4
	str r3, [r1], #4
	cmp r1, r2
	bne copy_loop
	
clear_bss:
	ldr r0, =bss_start
	ldr r1, =bss_end
	cmp r0, r1
	beq run_on_dram
	mov r2, #0
clear_loop:
	str r2, [r0], #4
	cmp r0, r1
	bne clear_loop
	
run_on_dram:
	ldr pc, =led_blink  // 长跳转到 C 语言中
	
	b.

SDRAM引入

SDRAM:Syncronized Dynamic Ramdam Access Memory,同步动态随机存储器。

DDR:DDR就是DDR SDRAM,是SDRAM的升级版。(DDR:double rate,双倍速度的SDRAM)。 DDR有好多代:DDR1 DDR2 DDR3 DDR4 LPDDR

SDRAM的特性

容量大、价格低、掉电易失性、随机读写、总线式访问。

SDRAM/DDR都属于动态内存(相对于静态内存SRAM),都需要先运行一段初始化代码来初始化才能使用。不像SRAM开机上电后就可以直接运行。

类似于SDRAM和SRAM的区别的,还有NorFlash和NandFlash(硬盘)这两个。

正是因为硬件本身特性有限制,所以才导致启动代码比较怪异、比较复杂。而我们研究裸机是为了研究uboot,在uboot中就充分利用了硬件的各种特性,处理了硬件复杂性。

SDRAM数据手册

SDRAM在系统中属于SoC外接设备(外部外设。以前说过随着半导体技术发展,很多东西都逐渐集成到SoC内部去了。现在还长期在外部的一般有:Flash、SDRAM/DDR、网卡芯片如DM9000、音频Codec。现在有一些高集成度的芯片也试图把这几个集成进去,做成真正的单芯片解决方案。)

SDRAM通过地址总线和数据总线接口(总线接口)与SoC通信。

开发板原理图上使用的是K4T1G164QQ,但是实际开发板上贴的不是这个,是另一款。但是这两款是完全兼容的,进行软件编程分析的时候完全可以参考K4T1G164QQ的文档。K4T1G164QE:K表示三星产品,4表示是DRAM,T表示产品号码,1G表示容量(1Gb,等于128MB,我们开发板X210上一共用了4片相同的内存,所以总容量是128×4=512MB)16表示单芯片是16位宽的,4表示是4bank。

SDRAM初始化

原理图中SDRAM相关部分

ddr

S5PV210共有2个内存端口(就好象有2个内存插槽)。再结合查阅数据手册中内存映射部分,可知:两个内存端口分别叫DRAM0和DRAM1:

  • DRAM0:内存地址范围:0x20000000~0x3FFFFFFF(512MB),对应引脚是 Xm1xxxx
  • DRAM1: 内存地址范围:0x40000000~0x7FFFFFFF(1024MB),对应引脚是 Xm2xxxx

结论:

  • 整个210最多支持内存为1.5GB,如果给210更多的内存CPU就无法识别。
  • 210最多支持1.5GB内存,但是实际开发板不一定要这么多,譬如我们X210开发板就只有512MB内存,连接方法是在DRAM0端口分布256MB,在DRAM1端口分布了256MB。
  • 由2可知,X210开发板上内存合法地址是:0x20000000~0x2FFFFFFF(256MB) + 0x40000000~0x4FFFFFFF(256MB)。当板子上DDR初始化完成之后,这些地址都是可以使用的;如果使用了其他地址譬如0x30004000就是死路一条。

ddr1

原理图中每个DDR端口都由3类总线构成:地址总线(Xmn_ADDR0~XMnADDR13 共14根地址总线) + 控制总线(中间部分) + 数据总线(Xmn_DATA0~XMnDATA31 共32根数据线)

分析:从数据总线的位数可以看出,我们用的是32位的(物理)内存。

原理图中画出4片内存芯片的一页,可以看出:X210开发板共使用了4片内存(每片1Gb=128MB,共512MB),每片内存的数据总线都是16位的(单芯片是16位内存)。如何由16位内存得到32位内存呢?可以使用并联方法。在原理图上横向的2颗内存芯片就是并联连接的。并联时地址总线接法一样,但是数据总线要加起来。这样连接相当于在逻辑上可以把这2颗内存芯片看成是一个(这一个芯片是32位的,接在Xm1端口上)。

数据手册中SDRAM相关部分

block

看数据手册《NT5TU64M16GG-DDR2-1G-G-R18-Consumer》第10页的block diagram。这个框图是128Bb×8结构的,这里的8指的是8bank,每bank128Mbit。

210的DDR端口信号中有BA0~BA2,接在内存芯片的BA0~BA2上,这些引脚就是用来选择bank的。每个bank内部有128Mb,通过row address(14位) + column address(10位)的方式来综合寻址。一共能寻址的范围是:2的14次方 x 2的10次方 = 2的24次方。对应16MB(128Mbit)内存。

汇编初始化SDRAM详解

DDR初始化和SoC(准确说是和SoC中的DDR控制器)有关,也和开发板使用的DDR芯片有关,和开发板设计时DDR的连接方式也有关。三星在官方的数据手册里(section 05 memory->DRAM CONTROLLER->1.2.1.3章节中)讲到DDR2的初始化过程,共27步。之前分析过X210的内存连接方式是:在DRAM0上连接256MB,在DRAM1上连接了256MB。所以初始化DRAM时分为2部分,第一部分初始化DRAM0,第二部分初始化DRAM1。

section 05 memory->DRAM CONTROLLER->1.4章节

设置IO端口驱动强度

因为DDR芯片和S5PV210之间是通过很多总线连接的,总线的物理表现就是很多个引脚,也就是说DDR芯片和S5PV210芯片是通过一些引脚连接的。DDR芯片工作时需要一定的驱动信号,这个驱动信号需要一定的电平水平才能抗干扰,所以需要设置这些引脚的驱动能力,使DDR正常工作。DRAM控制器对应的引脚设置为驱动强度2X。寄存器在《section 02 system->GENERAL PURPOSE INPUT/OUTPUT->2.2章节》

DRAM port 时钟设置

从代码第128行到154行。主要是开启DLL(dram pll)然后等待锁存。这段代码对应27步中的第2到第4步。内存芯片是没有时钟的,它的时钟信号是从 210 过来,然后在内部通过PLL进行倍频以获得更高的频率。210过来的频率可能只有200MHz,而内存的工作频率在1GHz以上。

DRAM 控制寄存器

DMC0_MEMCONTROL的值因burst length=4,1chip,・・・・・・ 对应值是0x00202400

dmc1

dmc2

DMC_DIRECTCMD

这个寄存器是个命令寄存器,我们210通过向这个寄存器写值来向DDR芯片发送命令(通过命令总线),这些命令应该都是用来配置DDR芯片工作参数。

重定位代码到SDRAM中

DRAM初始化之后,实际上重定位代码过程和之前重定位到SRAM中完全相同。