良许Linux教程网 干货合集 Linux内存管理:uboot如何为内核准备好内存环境

Linux内存管理:uboot如何为内核准备好内存环境

Linux系统中,uboot是一个主要用于嵌入式系统的引导加载程序,它可以支持多种不同的计算机系统结构和操作系统内核。uboot的一个重要功能是对内存芯片进行初始化、配置、开启等工作,为Linux内核启动做好准备。本文将介绍Linux内存管理之uboot第一步的基本步骤和方法,包括如何打开内存功能,配置内存,初始化内存设备,获得内存基本信息等。

在进入讲解linux内存管理的kernel阶段以前,了解一下uboot阶段是如何准备好内存物理设备的,这是非常有意义的。通常进入到linux内核阶段之后,对内存芯片的物理特性寄存器访问是比较少的,强调的是linux在管理上的用法,而大部分必要工作由uboot阶段进行处理,如打开内存功能,配置内存,初始化内存设备,获得内存基本信息等。
下面以笔记的形式讲述调试uboot内存的方法,分别以ARM芯片和MIPS芯片为基础进行,大家可以将其作为bringup板子时的参考。
一 分类:内存与CPU的连接方式分为两类,一类是Static OnBoard, 表示板载内存颗粒;一类是DIMM 双列直插模式连接;两者在硬件连接上有不同的地方,这里不是重点,但这两类配置内存的方式是有差别的,前者由启动代码对映射好的关于内存的寄存器进行直接配置,后者是由启动代码读取DIMM上的iic spd数据配置内存的。
二 内存的配置代码涉及到启动过程,因此我饶有兴趣地向大家讲讲启动过程。
uboot的开源性会提供一个启动汇编程序start.S, 对于一个具体的芯片,会由此start.S调用分支,执行关于它的一个具体平台platform.S, platform.S当中正包含了如何初始化内存的整个过程。
\1. arm方式的过程,针对static OnBoard方式,当然它也支持DIMM方式。
uboot的第一个阶段也正是执行该汇编程序的地方,包含start.S, platform.S, ddr2BasicInit/ddr2StaticInit.S….。
\1) 首先它要设置好文本段TEXT_BASE起始值,BSS段起止start/end,为后续定位执行做好准备。
\2) 然后刷新芯片内部I/D-Cache, 该Cache一般均为32K大小,由协处理器CP15管理,关闭MMU和TLB,刷新和关闭的目的是防止cpu使用这些硬件取不对应的数据值,比如没有刷新cache,新的存取操作已经发生,但不是cache原来的命中值,数据已经发生变化但cache仍然保留原样,这样就取出错误的数据和指令。
\3) 然后进入到cpu_init_crit,这个函数通常映射了具体平台platform.S, 执行标签为lowlevel_init,它完成的工作包括:映射CPU寄存器地址,使用CPU寄存器地址配置CPU关于总线的初始化(如MBUS),配置cpu的时钟和MPP调试GPIO口,再转入到内存的调试标签入口_mvDramIfStaticInit/_mvDramIfBasicInit, 这个文件在ddr2/mvDramIfBasicInit.S目录下,如果为static执行_mvDramIfStaticInit直接写配置字就完成,如果为DIMM,则初始化IIC即TWSI,_i2cRead读SPD,最后用读出的值来配置内存_mvDramIfConfig。这部分的执行流程过程如下图1-1所示:

img
img

图1-1 内存配置的启动代码流程图
下面列出一个关于Marvell 512M static 内存的配置情况如图1-2(这是俺熟读CPU关于内存寄存器配置很多遍得到的结果哦)

 #define STATIC_SDRAM0_BANK0_SIZE                0x1FFFFFF1 /*   0x1504  *//*0 --> 16*16,  1-->32*16*
  #define STATIC_SDRAM_CONFIG                     0x43008c30 /*   0x1400  */
  #define STATIC_SDRAM_MODE                       0x00000C52 /*   0x141c  */
  #define STATIC_DUNIT_CTRL_LOW                   0x39543000 /*   0x1404  */
  #define STATIC_DUNIT_CTRL_HI                    0x0000F1FF /*   0x1424  */
  #define STATIC_SDRAM_ADDR_CTRL                  0x0000000d /*   0x1410  */ /*512M*/
  #define STATIC_SDRAM_TIME_CTRL_LOW              0x22125451 /*   0x1408  */
  #define STATIC_SDRAM_TIME_CTRL_HI               0x00000833 /*   0x140c  */
  #define STATIC_SDRAM_ODT_CTRL_LOW               0x003C0000 /*   0x1494  */
  #define STATIC_SDRAM_ODT_CTRL_HI                0x00000000 /*   0x1498  */
  #define STATIC_SDRAM_DUNIT_ODT_CTRL             0x0000F80F /*   0x149c  */
  #define STATIC_SDRAM_EXT_MODE                   0x00000042 /*   0x1420  */
  #define STATIC_SDRAM_DDR2_TIMING_LO             0x00085520 /*   0x1428  */
  #define STATIC_SDRAM_DDR2_TIMING_HI             0x00008552 /*   0x147C  */

图1-2 板载内存配置

\4) 完成上面的3)过程,喘口气,它还没跳出第一阶段的圈子,还在汇编程序当中,但它准备好了最关键的设备--内存,后面的程序就可以使用它了。下面就进入uboot引导的关键一步----重定位,代码截图1-3如下:
relocate:                               /* relocate U-Boot to RAM           */
        adr     r0, _start              /* r0 test if we run from flash or RAM */
        cmp     r0, r1                  /* don't reloc during debug         */
        beq     stack_setup

        ldr     r2, _armboot_start
        ldr     r3, _bss_start
        sub     r2, r3, r2              /* r2 

图1-3 start.S中重定位
重定位的主要作用是将Nand/Nor flash上的代码拷贝到内存上来使用,当然,我们不可以简单理解这种拷贝是由读写flash函数调用来实现的,毕竟此时flash还没有被初始化,一般的理解是:CPU在出厂的时候会烧固件给内部ROM,它是上电可执行代码,像PC机的bios一样会自检等等。一般来讲,CPU内部也是有小部分RAM,当CPU的boot_cs选中外界flash的时候,CPU的flash controller会根据这个片选信号倾泻数据给RAM。重定位的作用正是这部分flash对RAM的倾泻转移到物理内存上,最终交由内存来存放uboot源码。1)步骤正是为了配合重定位来是指示bss和起始执行位置TEXT_BASE的,在Marvell芯片内部,uboot在物理内存重定位的值为0x600000 6M处位置,可用命令md 0x600000查看。
\5) start.S阶段当然还有一些工作需要处理,比如关闭中断,设置异常中断指向重启复位,重新安排stack的位置等等,这些都不是重点,了解即可。
到此 终于完成了它的第一阶段,现在进入uboot的第二步,我浮光掠影地讲解下第二阶段过程,重点放在内存上。
\1) 第一阶段调用start_armboot指向C语言执行代码区,首先它要从内存上的重定位数据获得不完全配置的全局数据表格和板级信息表格,即获得gd_t和bd_t,这两个类型变量记录了刚启动时的信息,并将要记录作为引导内核和文件系统的参数,如bootargs等等,并且将来还会在启动内核时,由uboot交由kernel时会有所用。
\2) 下面就是各个sequence的初始化,重点关注cpuMapInit和dram_init,对于Marvell芯片源码,cpuMapInit函数是要读取一张表格,这个表格用来映射CPU的地址空间,它包含内存的,设备的,启动boot_cs的,以及PCI设备的映射,由于uboot不分所谓的内核空间用户空间,也就无所谓权限保护的问题,这张表格指定是多少地址就是实际使用该设备的CPU空间地址,可以理解为直接映射,对于Marvell设备来讲,内存是零偏移位置,重要的是不要冲突占用即可。
dram_init的初始化,实际上由于Static OnBoard原因简化了操作,只是获取该内存的映射地址以及内存的大小,这些映射的设置亦有cpuMapInit完成,然后将内存的一些信息填充到gd_t和bd_t当中。
\3) 补充啰嗦一点,uboot第二阶段最初会关中断初始化的中断计数,待关键的内容初始化完成后(如cpu_init,cpuMap,env),会同GPIO一同打开中断功能。当然uboot的初始化远不止这些,还包括诸如flash初始化,scsi初始化,以太网初始化,定时器初始化等等。
第二阶段工作的完成,可以告知系统内存是线性而且是直接的映射,CPU可以在内存上做各种简单的应用,可以使用malloc函数分配,当然无法执行繁重的工作,因为它没有回收和分配管理能力。
2。MIPS方式过程:以spd为例,当然也支持OnBoard方式。
若以SPD方式来调试板子的bringup,其实相对来说是比较容易的,因为它的大部分信息已经记录在spd内部,读出来配置就可以了,能否成功配置同cpu的内存访问源码有关,通常要检查CPU是否正确指定了spd iic地址,其次,如果源码的健壮性比较差,还要根据CPU的关于内存的片选,内存的延迟机时做适当的调整,因为某些内存是双CS的,内存的rank是偶数,同样,layout会影响信号失真,配置芯片CPU的信号增强也可以提高稳定性。
下面列出一些我在MIPS平台下的数据如图1-4,大家可以作为参考,至于MIPS的bootloader,与ARM的大同小异,跟着上面一样的思路就可以完整找到具体的步骤,在此不啰嗦了。

Initializing DDR interface 0, DDR Clock 400000000, DDR Reference Clock 50000000
DIMM 0: DDR2 Unbuffered, non-ECCSPD dump for DIMM at TWSI address: 0x50
0 Number of Serial PD Bytes written during module production 0x80
1 Total number of Bytes in Serial PD device 0x08
2 Fundamental Memory Type (FPM, EDO, SDRAM, DDR, DDR2) 0x08
3 Number of Row Addresses on this assembly 0x0e
4 Number of Column Addresses on this assembly 0x0a
5 Number of DIMM Ranks 0x60
6 Data Width of this assembly 0x40
7 Reserved 0x00
8 Voltage Interface Level of this assembly 0x05
9 SDRAM Cycle time at Maximum Supported CAS Latency (CL), CL=X 0x25
10 SDRAM Access from Clock (tAC) 0x40
11 DIMM configuration type (Non-parity, Parity or ECC) 0x00
12 Refresh Rate/Type (tREFI) 0x82
13 Primary SDRAM Width 0x08
14 Error Checking SDRAM Width 0x00
15 Reserved 0x00
16 SDRAM Device Attributes: Burst Lengths Supported 0x0c
17 SDRAM Device Attributes: Number of Banks on SDRAM Device 0x08
18 SDRAM Device Attributes: CAS Latency 0x60
19 Reserved 0x01
20 DIMM Type Information 0x04
21 SDRAM Module Attributes 0x00
22 SDRAM Device Attributes: General 0x07
23 Minimum Clock Cycle at CLX-1 0x30
24 Maximum Data Access Time (tAC) from Clock at CLX-1 0x45
25 Minimum Clock Cycle at CLX-2 0x3d
26 Maximum Data Access Time (tAC) from Clock at CLX-2 0x50
27 Minimum Row Precharge Time (tRP) 0x3c
28 Minimum Row Active to Row Active delay (tRRD) 0x1e
29 Minimum RAS to CAS delay (tRCD) 0x3c
30 Minimum Active to Precharge Time (tRAS) 0x2d
31 Module Rank Density 0x01
32 Address and Command Input Setup Time Before Clock (tIS) 0x17
33 Address and Command Input Hold Time After Clock (tIH) 0x25
34 Data Input Setup Time Before Clock (tDS) 0x05
35 Data Input Hold Time After Clock (tDH) 0x12
36 Write recovery time (tWR) 0x3c
37 Internal write to read command delay (tWTR) 0x1e
38 Internal read to precharge command delay (tRTP) 0x1e
39 Memory Analysis Probe Characteristics 0x00
40 Extension of Byte 41 tRC and Byte 42 tRFC 0x36
41 SDRAM Device Minimum Active to Active/Auto Refresh Time (tRC) 0x39
42 SDRAM Min Auto-Ref to Active/Auto-Ref Command Period (tRFC) 0x7f
43 SDRAM Device Maximum device cycle time (tCKmax) 0x80
44 SDRAM Device maximum skew between DQS and DQ signals (tDQSQ) 0x14
45 SDRAM Device Maximum Read DataHold Skew Factor (tQHS) 0x1e
46 PLL Relock Time 0x00
47 IDD in SPD - To be defined 0x53

 

图1-4 spd 数据读

三。总结:uboot阶段的内存方面并没有管理特征,只是线性直接的映射,重点在于打通内存设备,让内存设备在物理上能够工作起来,并且能接管来自CPU内部RAM的数据重定位,从而执行外部设备的uboot二进制映象。文中提到了许多调试uboot的方法,这是工作的总结,在调试bringup板子上一定用的上。

通过本文,你应该对Linux内存管理之uboot第一步有了一个基本的了解,它是一种为内核准备好内存环境的有效方式,可以适应不同的硬件平台和内核版本。当然,uboot也不是一成不变的,它需要根据具体的芯片型号和功能需求进行定制和修改。总之,uboot是Linux系统中不可或缺的一个组件,值得你深入学习和掌握。

以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !

137e00002230ad9f26e78-265x300
本文由 良许Linux教程网 发布,可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
良许

作者: 良许

良许,世界500强企业Linux开发工程师,公众号【良许Linux】的作者,全网拥有超30W粉丝。个人标签:创业者,CSDN学院讲师,副业达人,流量玩家,摄影爱好者。
上一篇
下一篇

发表评论

联系我们

联系我们

公众号:良许Linux

在线咨询: QQ交谈

邮箱: yychuyu@163.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部