U-boot的README
学习Uboot的过程中发现很多教程更多在关注如何在uboot基础上进行开发,而没有关于框架本身运行的解释。
本文尝试从Uboot的README中提取有关框架本身的运行流程和其他信息,对部分内容进行翻译。
Uboot的README有5000多行(太长了)并且使用的markdown并没有很清晰的层级结构,本文概述部分试图梳理整个README结构,并对比较重要的内容在后文进行具体分析。
更新中:存在疑问的地方可能会出现作者自己的提问,最终会删除。
先用make version确认一下当前使用的uboot版本:
@vm:~/rockchip-bsp/u-boot$ make ubootversion
2017.09
概述
从README一级标题的视角出发,梳理README的结构。保证概述的简洁性。
- 项目基本信息介绍
- Summary:介绍uboot项目和目录,提及自己和Linux关系相近,并且具有很好的扩展性。
- Status:板级配置文件均经过测试且应当可用,贡献者请关注CHANGELOG。
- Where to get help:问题请联系指定邮箱。
- Where to get source code
- Where we come from:简单的说一下开发历史
- Names and Spelling:文档和注释中的Uboot应当写作“U-Boot”,文件名中写作“u-boot”,变量名和宏中写作“U_BOOT”
- Versioning:版本号格式
- Directory:项目目录结构,各个文件夹的作用
 
- Software Configuration:配置uboot
 uboot和Linux一样使用C的宏定义进行配置,其中以CONFIG_开头配置的由用户决定,CONFIG_SYS开头的应根据实际硬件谨慎调整。早期uboot的这些设置都需要手动配置,现在可以使用和Linux一样的Kconfig进行图形化配置(使用make menuconfig)。- Selection of Processor Architecture and Board Type:使用make <board_name>_defconfig加载所需开发板的配置,往往由厂商SDK提供。
- Sandbox Environment:Uboot可以在Linux主机中以sandbox board配置进行编译和调试,以实现在主机上开发uboot中与架构和开发板无关的功能(not board or architecture specific specific)
- Board Initialisation Flow:开发板初始化流程。比较有价值,见后文详述。
- Configuration Options:配置文件由板级配置文件和CPU级配置文件共同构成,二者均位于include/configs文件夹下,以<board_name>.h为命名格式。且配置文件中的选项很多都和Linux下的对应选项名称相同。
 首先需要配置CPU类型(例如CONFIG_MPC85XX),其次是开发板类型(例如CONFIG_MPC8540ADS),随后列出了非常多的具体选项,覆盖每种架构的CPU、外设、uboot功能等,可根据需要打开或配置。这部分占了2000多行(Line 316-2900),略读时可以跳过。
- Board initialization settings:初始化特定开发板所需要的额外函数和必须打开的宏
- Configuration Settings:列出了可以配置的设置
- Low Level (hardware related) configuration options:
- Freescale QE/FMAN Firmware Support:
- Freescale Layerscape Management Complex Firmware Support:
- Freescale Layerscape Debug Server Support:
 
- Reproducible builds:在编译环境变量中设置SOURCE_DATE_EPOCH作为编译时间戳。
 
- Building the Software:uboot编译流程,以及当没有合适的配置文件时应该怎么办
 Testing of U-Boot Modifications, Ports to New Hardware, etc.:移植时需要提交patch的操作方法。
- U-boot环境
- Monitor Commands - Overview & Detailed Description:uboot控制台命令行命令集
- Environment Variables:uboot内可以使用环境变量进行用户配置,列出了可用的环境变量
 - 环境变量可以绑定回调函数,当环境变量发生改变时uboot会调用回调函数
 - Command Line Parsing:命令行解析,支持传统模式(simple one)和hush shell。
 - 用冒号分隔并列的命令,前一个失败了就会执行下一个
 - Note for Redundant Ethernet Interfaces:uboot会在有多个网卡时自动选择可用的一个,具体描述了MAC地址配置规则
 
- Image Formats:镜像格式
 uboot支持两种启动格式:- New uImage format (FIT):基于Flattened Image Tree的新格式
- Old uImage format:传统格式,有固定的头部描述信息
 
- Linux Support
 uboot的设计出发点是为Linux服务的,但是理论上也可用于其他系统。
 主要提及了initrd和Linux内核二者分离设计的优点。
- Linux HOWTO
 以PPC架构为例,讲解了怎么将Linux移植到基于uboot的系统。- Configuring the Linux kernel:配置内核,不需要多做什么。
- Building a Linux Image:使用mkimage从vmlinux生成专用于uboot的uImage
- Installing a Linux Image:使用uboot在内存中载入和检查Linux Image。
- Boot Linux:使用bootm从内存指定地址载入Linux Image和initrd或设备树并启动
- More About U-Boot Image Types:uboot所支持的各种Image Type
- Booting the Linux zImage:使用bootz从Linux zImage启动
 
- Standalone HOWTO: 如何在uboot里动态载入独立程序
 举了一个Hello world的例子
- Minicom warning:不要用minicom作为串口下载工具,有问题。
- NetBSD Notes:NetBSD操作系统支持说明。
- Implementation Internals:uboot内部实现
- U-Boot Porting Guide:用类似C伪代码的形式描述移植U-boot的过程。
- Coding Standards:代码风格和标准要求
- Submitting Patches:提交Patch的方法
2.3 Board Initialisation Flow 详述
本小节描述开发板初始化流程(start-up flow),且应当对SPL和U-boot均适用,即SPL和Uboot中会有一模一样的函数和流程,但是二者具体执行的功能有差异。
注:SPL指Secondary Program Loader,是执行在uboot前,和uboot功能/流程相似的组件,具体在本文件后文解释。全局启动流程(?):SPL -> U-boot -> Linux 或 SPL -> Linux。并不是所有的SPL都遵循下面的流程,至少大多数ARM开发板在定义了CONFIG_SPL_FRAMEWORK时是这样。
Uboot的起点往往是某个架构,或是具体到某架构中某个CPU的start.S文件,例如:
- arch/arm/cpu/armv7/start.S
- arch/powerpc/cpu/mpc83xx/start.S
- arch/mips/cpu/start.S
接下来,依次调用三个函数:
- lowlevel_init():最基础的初始化(构建C语言环境?),保证代码可以执行到下一个函数board_init_f()。
 特点:无global_data或BSS;没有栈;不可使用SDRAM或控制台;进行最小程度的初始化,能让下一个函数执行就行;从不需要这个(this is almost never needed???);从函数中正常返回。
- board_init_f():让机器可以执行下一步board_init_r()
 特点:可以使用global_data了;在SRAM中有栈;BSS没有准备好,不可以使用全局或静态变量(这与BSS段相关),即代码中只能有global_data和栈变量。
 注1:如果SPL没有初始化DRAM,那么可以在Uboot里用dram_init()初始化DRAM。
 注2:在SPL环境下:可以自己根据需要覆写整个board_init_f()函数;极端者可以在这里就调用preload_console_init()(不应在这里使用console?);不要手动清除BSS,因为crt0.S会做这件事;必须从本函数正常返回,不可提前调用下一步的board_init_r()。
 到这里BSS会被清除,如果使用了SPL且定义了CONFIG_SPL_STACK_R,栈和global_data会被重定向到指定的CONFIG_SPL_STACK_R_ADDR位置;如果没使用SPL,Uboot会被重定向到内存首部。
- board_init_r():uboot的主函数(main execution),一般代码
 特点:可以使用global_data;可以使用SDRAM;可以使用BSS,即可以使用所有的全局或静态变量;最终代码执行到main_loop(),等待用户控制台输入或者跳过输入开始载入操作系统(如Linux)。
 注1:如果没有使用SPL,uboot会重定向到内存首部并且从首部开始执行。
 注2:如果使用了SPL且定义了CONFIG_SPL_STACK_R和指向SDRAM的CONFIG_SPL_STACK_R_ADDR,SDRAM中的栈就是可选的;可以在这里调用preloader_console_init(),不过如果定义了CONFIG_SPL_BOARD_INIT并且提供了spl_board_init()函数,相同的工作会由后者完成;最终在此处载入U-boot或Linux。
10 Implementation Internals 详述
接下来的描述并不能完全覆盖每一处实现,但是可以帮助理解uboot的内部工作方式,从而让移植工作更简单。
Initial Stack, Global Data
UBoot的实现是复杂的,而这往往是因为UBoot在ROM之外运行,且无法访问系统RAM(因为内存控制器还没有被初始化),这意味着我们没有可写入的数据区域或BSS区域,并且BSS也没有被初始化为0。为了让C语言环境可以工作,我们至少需要分配一个最小的栈。而实现这一目标又受到所使用的CPU的定义或限制:有些CPU提供片上内存,另一些CPU可以把一部分Data Cache锁住当内存用。
(下面列了一条Chris Hallinan的回信作为总结,用非内存区域当内存中的栈使用)
必须记住下面这些原则,因为它们影响到了初始化过程中的C代码:
- 初始化后的全局数据(数据段)是只读的,不要尝试写入它们。
- 不要使用任何没有初始化的全局数据(或是理论上应当被置零的BSS区域),会出现未经定义的行为。而初始化过程会在后面完成(当完成数据迁移到RAM后(when relocating to RAM))。
- 栈空间非常有限,不要在栈内使用big data buffers。
 只能在栈里写入意味着我们不能使用一般的全局数据来在代码间共享信息。但是事实又证明如果存在一个全局数据结构体(global data structure, gd_t)可以使用的话,Uboot的实现会简单很多。我们可以向所有函数传递一个指针,但这会使得代码变得冗余(bloated)。所以取而代之,我们使用了GCC编译器的一个特性(全局寄存器变量),在一个固定的寄存器里放置了一个指针(gd)来共享数据。
 (下面列出了不同架构中用于存放gd指针的寄存器,例如ARM里是R9)
Memory Management
U-Boot在系统态运行并且使用的是物理地址,即并没有使用MMU来映射地址或保护内存。
使用内存控制器将可用内存映射到固定的地址上。在这个过程中,不同类型的存储介质(Flash, SDRAM, SRAM)被划分为多个连续的块(contiguous block),即使物理上它们有多个bank。
而U-boot则被安装在第一个块的前128KB空间上。当启动、测量(size)和初始化DRAM结束后,代码将自己移动到DRAM的顶端(upper end),而U-Boot代码下方的部分内存则预留给malloc()使用,再下面是一个存储全局开发板信息的结构体,再之后是栈。
此外,部分异常处理代码(exception handler code)被复制到DRAM低8kB的位置。
从而传统16MB DRAM的分配结构是下面这样:
	0x0000 0000	Exception Vector code(异常向量代码)
	      :
	0x0000 1FFF
	0x0000 2000	Free for Application Use(供应用自由使用)
	      :
	      :
	      :
	      :
	0x00FB FF20	Monitor Stack (Growing downward)(uboot栈,向下增长)
	0x00FB FFAC	Board Info Data and permanent copy of global data(GD)
	0x00FC 0000	Malloc Arena(Malloc预留)
	      :
	0x00FD FFFF
	0x00FE 0000	RAM Copy of Monitor Code(uboot代码)
	...		eventually: LCD or video framebuffer
	...		eventually: pRAM (Protected RAM - unchanged by reset)
	0x00FF FFFF	[End of RAM]
注:Monitor在这里应该是uboot控制台之类的意思,总之不是显示器(
System Initialization (以PowerPC架构为例)
在复位过程(reset configuration)中,U-Boot从复位入口(reset entry point)开始运行,对大多数PowerPC系统这个地址是0x00000100。
因为复位过程中CS0#是板上Flash的镜像,为了re-map内存U-Boot会跳转到它的链接地址。为了运行用C语言写的初始化代码,必须在CPU的片内RAM或部分锁定后的data cache中建立一个很小的初始化栈。这之后,U-Boot初始化CPU核心、caches和SIU。
接下来,所有的内存bank都会被初步映射到一个地址上。例如我们将512MB设为边界,接下来UPM A会访问SDRAM,使用临时配置进行一个简单的内存测试来测量(size)SDRAM的实际大小。
当有多个SDRAM bank时,如果它们大小都不同,最大的那个会首先被映射(mapped);如果它们等大,第一个(CS2#)会被映射。第一个bank首先会被映射到0x00000000,其余的接续前者的地址进行映射,最后的内存空间是连续的。
接下来,monitor(uboot)将自己复制到SDRAM的顶部区域并且给malloc()和全局开发板信息分配空间,异常向量代码复制到RAM的低地址页上,然后建立最后的栈。
只有在relocation完成后你才拥有一个“一般的(normal)”C语言环境;在这之前你都会受到各种方面的限制,或是因为你从ROM开始运行,又或是因为代码最后会被relocate到RAM的新地址。
 
                    
                
 
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号