17.初始化SDRAM和代码重定位

来源:互联网 发布:java可以使用html5吗 编辑:程序博客网 时间:2024/05/19 03:42

17.1.代码重定位原理分析
(1)在SRAM中将代码从0xd0020010重定位到0xd0024000,本来代码是运行在0xd0020010的,但是我们又希望代码实际是在0xd0024000位置运行,此刻就需要重定位了,此次重定位实践纯粹是为了练习重定位技能,但某些情况重定位就是必须的,譬如在uboot中。
(2)通过链接脚本将代码链接到0xd0024000;通过dnw下载时将bin文件下载到0xd0020010/BL0直接从SD卡中读取bin文件到0xd0020010处;保证代码实际下载运行在0xd0020010,但一开始就被链接在0xd0024000,从而实现简单的重定位。
(3)我们把代码链接地址设置为0xd0024000(该段代码将来必须放在0xd0024000位置才能正确执行,若实际运行地址不是该地址就要出事(除非代码是PIC位置无关码));重定位代码的实质->在PIC执行完之前(在代码中第1句位置有关码执行之前)必须将整个代码搬移到0xd0024000位置去执行(代码执行时通过代码前段的少量位置无关码将整个代码搬移到0xd0024000;然后使用1个长跳转指令跳转到0xd0024000处的代码继续执行,重定位完成)。
(4)长跳转->首先该行代码是1句跳转指令(ARM中的跳转指令作用类似于分支指令B和BL),跳转指令通过给PC(r15)赋1个新值来完成代码段的跳转执行,长跳转指的是跳转到的地址和当前地址差异比较大,跳转的范围比较宽广;当我们执行完代码重定位后,实际上在SRAM中有2份代码的镜像(1份是我们下载到0xd0020010处开头的,另1份是重定位代码复制到0xd0024000处开头的),这两份内容完全相同,仅仅代码起始地址不同。
(5)短跳转和长跳转的区别->重定位之后使用”ldr-pc,=water_lights”该句长跳转指令从0xd0020010开头的代码处直接跳转到0xd0024000开头的代码的water_lights函数处去执行(实际上在SRAM中有2个water_lights函数镜像,两个都能执行,如果短跳转”bl-water_lights”则执行的是0xd0020010开头的这1份,如果长跳转”ldr-pc,=water_lights”则执行的是0xd0024000开头处的这1份)。
(6)当链接地址和运行地址相同时,短跳转和长跳转实际效果是一样的;但是当链接地址不等于运行地址时,短跳转实际执行的是运行地址处的那1份代码,而长跳转执行的是链接地址处那1份代码。重定位实际就是在运行地址处执行1段位置无关码PIC,让这段PIC(重定位代码)从运行地址处把整个程序镜像拷贝1份到链接地址处,然后使用1句长跳转指令从运行地址处直接跳转到链接地址处去执行同1个函数(water_lights)。


17.2.代码重定位实践
(1)adr与ldr伪指令的区别->ldr和adr都是伪指令,区别是ldr是长加载,adr是短加载(adr指令加载符号地址,加载的是运行时地址;ldr指令加载符号地址,加载的是链接地址)。
(2)重定位(代码拷贝)->重定位的实现即汇编代码中的copy_loop函数,该函数的作用是使用循环结构来逐句复制代码到链接地址;复制的源地址是SRAM的0xd0020010,复制目标地址是SRAM的0xd0024000,复制长度是bss_start减去_start;则复制的长度是整个重定位需要重定位的代码长度,即整个程序中代码段+数据段的长度,bss段(bss段中就是0初始化的全局变量)不需要重定位。
(3)清bss段->清除bss段是为了满足C语言的运行时要求(C语言要求显式初始化为0的全局变量,或者未显式初始化的全局变量的值为0,实际上C语言编译器就是通过清bss段来实现C语言的该特性的);我们的程序在平常是不需要负责清零bss段的(C语言编译器和链接器会帮我们的程序自动添加1段头程序,该段头程序会在我们的main函数之前运行,该段代码就负责清除bss);在我们代码重定位了之后,因为编译器帮我们附加的代码只是帮我们清除了运行地址那1份代码中的bss,而未清除重定位地址处开头的那1份代码的bss,则重定位之后需要自己去清除bss。
(4)长跳转->清理完bss段后重定位就结束了;此时当前运行地址还在0xd0020010开头的(重定位前的)那1份代码中运行着;此时SRAM中已经有了2份代码,1份在d0020010开头,另1份在d0024000开头的位置;然后就要长跳转了(ldr-pc,=water_lights)。


17.3.SDRAM初步引入
(1)SDRAM(同步动态随机存储器);DDR(双倍速度的SDRAM,是SDRAM的升级版,DDR有好多代->DDR1+DDR2+DDR3+DDR4+LPDDR);SDRAM的特性(容量大+价格低+掉电易失性+随机读写+总线式访问);SDRAM/DDR都属于动态内存(相对于静态内存SRAM),都需要先运行初始化代码进行初始化OK后才能正常使用,不像SRAM开机上电后就可以直接运行(类似于SDRAM和SRAM的区别的,还有NorFlash和NandFlash(硬盘))。
(2)SDRAM在系统中属于SoC外接设备;现在还长期在SoC外部的外设为Flash+SDRAM/DDR+网卡芯片如DM9000+音频Codec;SDRAM通过地址总线接口和数据总线接口与SoC通信。
(3)GEC210开发板上的DDR的丝印型号为”K4T1G164QE”(128MB字节);全球做SDRAM的厂商不多,二线厂家做的产品参数都是向一线厂家(三星/KingSton)看齐,目的是兼容一线厂家的设计,然后让在意成本的厂商选择它的内存芯片替代一线厂家的内存芯片;SDRAM的市场特征导致其中比较标准化,大部分时候细节参数官方(芯片原厂)都会提供参考值给你。
(4)K4T1G164QE->K表示三星产品;4表示是DRAM;T表示产品号码;1G表示容量(1Gb=128MB,GEC210开发板上共用了4片相同的内存,总容量=128MB×4=512MB字节);16表示单芯片是16位宽的;4表示是8bank。
(5)核心板原理图中SDRAM相关部分->S5PV210共有2个内存端口(Memory_Port1和Memory_Port2);再结合查阅数据手册中内存映射部分->两个内存端口分别叫DRAM0(内存地址范围0x20000000~0x3FFFFFFF(512MB),对应引脚是Xm1xxxx)和DRAM1(内存地址范围0x40000000~0x7FFFFFFF(1024MB),对应引脚是Xm2xxxx)。
(6)整个210最多支持内存为1.5GB,如果给210更多的内存CPU就无法识别;GEC210开发板上512MB内存的连接方法是在DRAM0端口分布256MB,在DRAM1端口分布256MB;GEC210开发板上内存合法地址为0x20000000~0x2FFFFFFF(256MB)+0x40000000~0x4FFFFFFF(256MB);当板子上DDR初始化完成之后,这些地址都是可以正常使用的。


17.4.SDRAM矩阵式寻址方式
(1)在核心板上的SoC的内存端口(MemoryPort)外设的原理图->每个DDR端口都由3类总线构成=地址总线(XMnADDR0~XMnADDR12共13根地址线)+控制总线+数据总线(XMnDATA0~XMnDATA31共32根数据线)。
(2)GEC210开发板共使用了4片内存(每片1Gb=128MB);每片内存的数据总线都是16位的(单芯片是16位内存);在原理图上横向的2颗内存芯片是并联连接的,并联时地址总线接法一样,但是数据总线要加起来,在逻辑上即可以把这2颗16位的内存芯片看成是1颗32位的内存芯片连接在端口上。
(3)在SDRAM中寻址是首先通过bank寻址线寻址到某个具体的bank;然后在具体的某个bank中再通过行地址(row-address)和列地址(column-address)寻址到某个具体的存储单元(一般的存储单元的大小为4bit/8bit/16bit);行列地址值由相应的地址线引脚分时复用得到。
(4)譬如GEC210中的某块”K4T1G164QE”内存芯片;此芯片有容量为128MB=8bank*16MB=8bank*64Mb*16bit(存储单元为16bit);210的DDR端口信号中有BA0~BA2,接在内存芯片的BA0~BA2上(该3个引脚即”bank寻址线”,用来选择bank);每个bank通过行地址(13位)+列地址(10位)的方式来综合寻址(矩阵式寻址);则一共能寻址的范围->2^13*2^10*16bit=2的24次方;即2^7Mbit=2^4MB(对应16MB(128Mbit)内存)。
(5)参考链接->http://www.crifan.com/summary_embedded_peripherals_sdram/。


17.5.汇编初始化SDRAM
(1)汇编初始化SDRAM是通过sdram_asm_init汇编函数在sdram_init.S文件中实现的(汇编实现的函数在返回时需要明确使用返回指令mov-pc,lr);DDR初始化和SoC中的DDR控制器有关;也和开发板使用的DDR芯片有关;也和开发板设计时DDR的连接方式有关。
(2)S5PV210的DDR初始化步骤在SoC数据手册(1.2.1.3-DDR2章节)可知初始化DDR共需27个步骤;GEC210的内存连接方式->在DRAM0上连接256MB+在DRAM1上连接了256MB,则第1部分初始化DRAM0+第2部分初始化DRAM1;我们的代码来源=GEC210开发板官方的uboot源码+参考了GEC210裸机教程中对DDR的初始化+根据自己的理解修改某些参数。
(3)设置IO端口驱动强度->因为DDR芯片和S5PV210之间是通过很多总线连接的,总线的物理表现就是很多个引脚,也就是说DDR芯片和S5PV210芯片是通过一些引脚连接的,DDR芯片工作时需要一定的驱动信号,这个驱动信号需要一定的电平水平才能抗干扰,所以需要设置这些引脚的驱动能力,使DDR正常工作(S5PV210中的DRAM控制器对应的引脚设置为驱动强度2X,这些参数由DDR芯片厂商或者SoC厂商提供,我们一般是参考原厂给的代码)。
(4)DDR配置过程比较复杂,基本上是按照DDR控制器的时序要求来做的,其中很多参数要结合DDR芯片本身的参数来定,还有些参数是时序参数,要去详细计算,所以DDR配置非常繁琐+细致+专业,所以我们只需要学会这种思路和方法,结合文档和代码能看懂,会算一些常见的参数即可(见图1和图3)。


这里写图片描述


这里写图片描述


这里写图片描述


17.relocate_sram/start.S/* * 公司:XXXX * 作者:Rston * 博客:http://blog.csdn.net/rston * GitHub:https://github.com/rston * 项目:初始化SDRAM和代码重定位 * 功能:演示在SRAM中实现重定位。 */#define WTCON       0xE2700000#define SVC_STACK   0xD0037D80.global _start              // 把_start链接属性改为外部,则外部其它文件可看见_start _start:    // 关闭看门狗    ldr r0, =WTCON    ldr r1, =0x0    str r1, [r0]    // 设置SVC_STACK栈    ldr sp, =SVC_STACK    // 关闭或开的icache    mrc p15,0,r0,c1,c0,0;   // 读出协处理器C1     //bic r0, r0, #(1<<12)  // 第12位置0,关闭icache    orr r0, r0, #(1<<12)    // 第12位置1,打开icache    mcr p15,0,r0,c1,c0,0;   // 给C1赋值    // 重定位    adr r0, _start          // adr指令加载_start运行时地址           ldr r1, =_start         // ldr指令用于加载_start的链接地址     ldr r2, =bss_start      // bss段的起始地址,此地址为链接地址    cmp r0, r1              // 比较_start的运行时地址和链接地址是否相等    beq clean_bss           // 若相等则不需重定位,则跳过copy_loop,直接clean_bsscopy_loop:    ldr r3, [r0], #4        // 运行时地址的源代码内容,即_start运行时地址的内容    str r3, [r1], #4        // 拷贝到链接地址处形成第2份源代码,完成4个字节内容的拷贝    cmp r1, r2              // r1r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2    bne copy_loopclean_bss:                  // 清bss段,其实就是在链接地址处把bss段全部清零    ldr r0, =bss_start                      ldr r1, =bss_end    cmp r0, r1              // 如果r0等于r1,说明bss段为空,直接下去    beq run_on_dram         // 清除bss完之后的地址    mov r2, #0clear_loop:    str r2, [r0], #4        // 先将r2中的值放入r0所指向的内存地址,然后r0 = r0 + 4    cmp r0, r1                  bne clear_loop          run_on_dram:        ldr pc, =water_lights   // ldr指令实现长跳转    // 汇编最后的这个死循环不能丢    b .                     

17.sdram_init/sdram_init.S// 汇编实现DRAM0的初始化#include "s5pv210.h"// 各种配置参数的宏名化#if 1#define DMC0_MEMCONTROL     0x00202400  // MemControl   BL=4, 1Chip, DDR2 Type, dynamic self refresh, force precharge, dynamic power down off#define DMC0_MEMCONFIG_0    0x20F00313  // MemConfig0   Row Address Bits 13 bits,Linear ({bank, row, column, width}) #define DMC0_MEMCONFIG_1    0x30F00312  // MemConfig1   默认值#define DMC0_TIMINGA_REF    0x00000618  // TimingAref   7.8us*133MHz=1038(0x40E), 100MHz=780(0x30C), 20MHz=156(0x9C), 10MHz=78(0x4E)#define DMC0_TIMING_ROW     0x28233287  // TimingRow    for @200MHz#define DMC0_TIMING_DATA    0x23240304  // TimingData   CL=3#define DMC0_TIMING_PWR     0x09C80232  // TimingPower#define DMC1_MEMCONTROL     0x00202400  // MemControl   BL=4, 2 chip, DDR2 type, dynamic self refresh, force precharge, dynamic power down off#define DMC1_MEMCONFIG_0    0x40F01323  // MemConfig0   512MB config, 8 banks,Mapping Method[12:15]0:linear, 1:linterleaved, 2:Mixed#define DMC1_MEMCONFIG_1    0x60E00312  // MemConfig1#define DMC1_TIMINGA_REF    0x00000618  // TimingAref   7.8us*133MHz=1038(0x40E), 100MHz=780(0x30C), 20MHz=156(0x9C), 10MHz=78(0x4#define DMC1_TIMING_ROW     0x28233289  // TimingRow    for @200MHz#define DMC1_TIMING_DATA    0x23240304  // TimingData   CL=3#define DMC1_TIMING_PWR     0x08280232  // TimingPower#endif// 声明sdram_asm_init函数能被外部调用.global sdram_asm_initsdram_asm_init:     // 估计只有三星原厂工程师知道,直接照抄    ldr r0, =0xf1e00000                     ldr r1, =0x0    str r1, [r0, #0x0]    // 设置IO总线驱动电平保证DRAM驱动信号能够正常传输,起到抗干扰的作用    // 默认设置,不用管,照抄即可,注意不可删除DMC1的驱动电平设置,若删除,则程序无法正常运行    /* DMC0 Drive Strength (Setting 2X) */    ldr r0, =ELFIN_GPIO_BASE    ldr r1, =0x0000AAAA    str r1, [r0, #MP1_0DRV_SR_OFFSET]           ldr r1, =0x0000AAAA    str r1, [r0, #MP1_1DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP1_2DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP1_3DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP1_4DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP1_5DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP1_6DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP1_7DRV_SR_OFFSET]    ldr r1, =0x00002AAA    str r1, [r0, #MP1_8DRV_SR_OFFSET]    // 设置IO总线驱动电平保证DRAM驱动信号能够正常传输,起到抗干扰的作用    // 默认设置,不用管,照抄即可,注意不可删除DMC1的驱动电平设置,若删除,则程序无法正常运行    /* DMC1 Drive Strength (Setting 2X) */    ldr r0, =ELFIN_GPIO_BASE    ldr r1, =0x0000AAAA    str r1, [r0, #MP2_0DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP2_1DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP2_2DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP2_3DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP2_4DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP2_5DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP2_6DRV_SR_OFFSET]    ldr r1, =0x0000AAAA    str r1, [r0, #MP2_7DRV_SR_OFFSET]    ldr r1, =0x00002AAA    str r1, [r0, #MP2_8DRV_SR_OFFSET]    // 开启dll(时钟倍频器)然后等待锁存,默认设置,不用管,照抄即可    /* DMC0 initialization at single Type*/    ldr r0, =APB_DMC_0_BASE    ldr r1, =0x00101000             @PhyControl0 DLL parameter setting, manual 0x00101000    str r1, [r0, #DMC_PHYCONTROL0]    ldr r1, =0x00000086             @PhyControl1 DLL parameter setting, LPDDR/LPDDR2 Case    str r1, [r0, #DMC_PHYCONTROL1]    ldr r1, =0x00101002             @PhyControl0 DLL on    str r1, [r0, #DMC_PHYCONTROL0]    ldr r1, =0x00101003             @PhyControl0 DLL start    str r1, [r0, #DMC_PHYCONTROL0]find_lock_val:    ldr r1, [r0, #DMC_PHYSTATUS]    @Load Phystatus register value    and r2, r1, #0x7    cmp r2, #0x7                    @Loop until DLL is locked    bne find_lock_val    and r1, #0x3fc0     mov r2, r1, LSL #18    orr r2, r2, #0x100000    orr r2 ,r2, #0x1000     orr r1, r2, #0x3                @Force Value locking    str r1, [r0, #DMC_PHYCONTROL0]    // 设置初始化DDR2相应的寄存器    /* setting DDR2 */    // 设置DRAM相关时序的寄存器,估计除了原厂的人没有人能搞懂,直接参考原厂设置即可    ldr r1, =0x0FFF2010             @ConControl auto refresh off    str r1, [r0, #DMC_CONCONTROL]    // 设置DRAM规格的控制器,GEC210开发板和X210开发板设置相同    ldr r1, =DMC0_MEMCONTROL        @MemControl BL=4, 1 chip, DDR2 type, dynamic self refresh, force precharge, dynamic power down off    str r1, [r0, #DMC_MEMCONTROL]    // 设置DRAM起始地址+内存容量大小+bank数目+行列地址    // 此项设置与开发板使用的DDR芯片有关+也和开发板设计时DDR的连接方式有关    // GEC210开发板和X210开发板在行列地址的位数设置不同    ldr r1, =DMC0_MEMCONFIG_0       @MemConfig0 Row Address Bits 13 bits,Linear ({bank, row, column, width})  1:linterleaved, 2:Mixed    str r1, [r0, #DMC_MEMCONFIG0]    ldr r1, =DMC0_MEMCONFIG_1       @MemConfig1    str r1, [r0, #DMC_MEMCONFIG1]    // 估计只有三星原厂工程师知道,直接照抄    ldr r1, =0xFF000000             @PrechConfig    str r1, [r0, #DMC_PRECHCONFIG]    // 需参考数据手册和时钟系统计算得到相应的参数    // TimingAref   7.8us*200MHz=1560(0x618)    ldr r1, =DMC0_TIMINGA_REF       @TimingAref 7.8us*133MHz=1038(0x40E), 100MHz=780(0x30C), 20MHz=156(0x9C), 10MHz=78(0x4E)    str r1, [r0, #DMC_TIMINGAREF]    // 以下设置时钟的代码,一般默认不改变,可参考时钟体系得到DDR的时钟来源为200MHz    ldr r1, =DMC0_TIMING_ROW        @TimingRow  for @200MHz    str r1, [r0, #DMC_TIMINGROW]    ldr r1, =DMC0_TIMING_DATA       @TimingData CL=3    str r1, [r0, #DMC_TIMINGDATA]    ldr r1, =DMC0_TIMING_PWR        @TimingPower    str r1, [r0, #DMC_TIMINGPOWER]    // 以下代码均为给DRAM发送相应的命令,可参看210手册中的27步命令,默认不改变    ldr r1, =0x07000000             @DirectCmd  chip0 Deselect    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x01000000             @DirectCmd  chip0 PALL    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00020000             @DirectCmd  chip0 EMRS2    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00030000             @DirectCmd  chip0 EMRS3    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00010400             @DirectCmd  chip0 EMRS1 (MEM DLL on, DQS# disable)    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00000542             @DirectCmd  chip0 MRS (MEM DLL reset) CL=4, BL=4    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x01000000             @DirectCmd  chip0 PALL    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x05000000             @DirectCmd  chip0 REFA    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x05000000             @DirectCmd  chip0 REFA    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00000442             @DirectCmd  chip0 MRS (MEM DLL unreset)    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00010780             @DirectCmd  chip0 EMRS1 (OCD default)    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00010400             @DirectCmd  chip0 EMRS1 (OCD exit)    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x07100000             @DirectCmd  chip1 Deselect    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x01100000             @DirectCmd  chip1 PALL    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00120000             @DirectCmd  chip1 EMRS2    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00130000             @DirectCmd  chip1 EMRS3    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00110400             @DirectCmd  chip1 EMRS1 (MEM DLL on, DQS# disable)    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00100542             @DirectCmd  chip1 MRS (MEM DLL reset) CL=4, BL=4    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x01100000             @DirectCmd  chip1 PALL    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x05100000             @DirectCmd  chip1 REFA    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x05100000             @DirectCmd  chip1 REFA    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00100442             @DirectCmd  chip1 MRS (MEM DLL unreset)    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00110780             @DirectCmd  chip1 EMRS1 (OCD default)    str r1, [r0, #DMC_DIRECTCMD]    ldr r1, =0x00110400             @DirectCmd  chip1 EMRS1 (OCD exit)    str r1, [r0, #DMC_DIRECTCMD]    // 以下为扫尾的代码,一般不去动,可适当注意下DMC_MEMCONTROL设置是否与前面设置相同       ldr r1, =0x0FF02030             @ConControl auto refresh on    str r1, [r0, #DMC_CONCONTROL]    ldr r1, =0xFFFF00FF             @PwrdnConfig    str r1, [r0, #DMC_PWRDNCONFIG]    ldr r1, =0x00202400             @MemControl BL=4, 2 chip, DDR2 type, dynamic self refresh, force precharge, dynamic power down off    str r1, [r0, #DMC_MEMCONTROL]    // 函数返回    mov pc, lr

0 0
原创粉丝点击