1. uboot概述
1)uboot的版本
A. 官方版本
B. SoC厂商版本
各SoC厂商(e.g. 三星)在推出一款SoC的同时会提供官方的开发套件,其中就包含SoC厂商提供的uboot。
大型SoC厂商还会将官方开发板的BSP提交到uboot官方版本中
C. 开发板厂商版本
目前世面上基于S5PV210 SoC的开发板都是在三星官方开发板的基础上修改而来,一般会对硬件接口进行裁剪,同时替换为更廉价的配件(e.g. DDR)。此时开发板厂商需要在SoC厂商提供的uboot基础上进行修改,以适配自身的产品
说明:我们后续分析的就是基于x210开发板的uboot,属于开发板厂商版本
2)uboot功能框架
uboot一般具有一下功能:
A. 引导系统(完成硬件的初始化)
iROM中仅会针对SoC进行简单初始化,后续针对各款开发板的初始化需要uboot完成
B. 运行操作系统(加载Linux内核并运行)
这是uboot最主要的功能,之前的初始化也是为Linux内核的加载运行准备环境
C. Flash烧写(更新系统Flash中的内容)
Flash烧写功能用于实现整个系统的部署功能,所谓系统部署就是完成整个系统(包括uboot、Linux kernel、rootfs等镜像)在Flash上的下载和烧录
D. 通信(串口、网络、USB)
E. 人机交互界面(基于命令行的交互功能)
3)uboot生命周期
以S5PV210芯片为例,
开始:iROM中的BL0从SD卡/iNand中拷贝BL1并运行
结束:启动内核,交出控制权
2. uboot编译流程
以x210开发板的uboot为例,
step 1:配置
make x210_sd_config
step 2:设置交叉工具链
修改uboot顶级目录下主Makefile中arm体系架构的CROSS_COMPILE变量,指定交叉工具链前缀
注意:如果已将交叉工具链路径加入PATH环境变量,此处可以不用绝对路径
step 3:编译
make [-j4]
说明:uboot清理流程
所谓清理,就是删除uboot之前配置和编译生成的文件,uboot中的make clean / make mrproper / make distclean可以实现不同层次的清理
make clean:Remove most generated files but keep the config and enough build support to build external modules
make mrproper:Remove all generated files + config + various backup files
make distclean:mrproper + remove editor backup and patch files
可以在配置编译uboot前执行make distclean,以清除可能存在的编译残留文件
3. x210对uboot.bin的使用
1)配置原因
x210对uboot.bin的处理是为了适应S5PV210的启动流程,在此简要复述(详情见ARM体系结构编程中相关文档)
A. S5PV210建议启动流程
① 将bootloader划分为BL1和BL2,iROM加载BL1(最大16KB)至iRAM中运行
② BL1将BL2从存储设备也拷贝到iRAM运行
③ 在BL2中进行内存初始化等操作,让后将Linux kernel拷贝到内存中,并跳转到内存启动内核
B. x210使用的启动流程
① 从uboot.bin中截取前8KB作为BL1,将整个uboot.bin作为BL2
② iROM加载BL1到iRAM中运行
③ BL1完成内存初始化并将BL2,即整个uboot.bin从iNand拷贝到uboot.bin的链接地址处(使用iROM提供的存储设备拷贝函数)
④ 跳转到内存中接着运行BL2
⑤ BL2完成进一步初始化,将Linux kernel拷贝到内存中,并启动内核
说明1:适配S5PV210的各版本uboot基本都未使用三星推荐的启动流程,这是因为uboot.bin的镜像普遍超过96KB,iRAM根本无法容纳其运行
S5PV210之所以选择这种启动方式,是因为在前一代产品S3C6410中三星引入了iROM + iRAM的启动方式,但当时的Stepping Stone只有8KB,所以只能容纳BL1运行,BL2只能加载到内存运行。
因此在S5PV210中增加了iRAM容量,希望能够将BL2也纳入其中运行(额~~最后只能是希望了~~)
说明2:BL1的生成方式
x210是从整个uboot.bin中截取8KB并校验加头构成BL1,这种方法的好处是只需要维护一份代码,使用一种编译方式生成一个镜像
其实官方uboot中也支持单独生成BL1镜像,这就需要用到CONFIG_SPL_BUILD配置项
2)sd_fusing目录文件分析
A. 目录结构
根据Makefile,
① mkbl1由C110-EVT1-mkbl1.c编译而来,用于生成BL1
② sd_fdisk由sd_fdisk.c编译而来,用于生成分区表
B. sd_fdisk.c文件分析
① struct SDInfo
该结构用于描述一张SD卡的信息,首先要注意的是addr_mode字段,该字段用于标识当前SD卡的地址模式是CHS_MODE还是LBA_MODE。CHS_MODE使用磁头-柱面-扇区号来寻址一个block;LBA_MODE使用逻辑块地址来寻址一个block。
代码中对地址模式的判断标准为SD卡的block总数是否超过1023 * 255 * 63
② struct PartitionInfo
该结构用于描述一个DPT分区表表项,此处介绍下DPT分区表及其相关概念
a. Boot Sector(引导扇区)
Boot Sector(引导扇区)通常指设备的第一个扇区(是整个磁盘的第一个扇区,需要区别于分区引导扇区),在PC上BIOS会读取该扇区,并将控制权转交给Boot Sector上的MBR。
Boot Sector包含3部分内容:
MBR(Master Boot Record,主引导记录),446B
DPT(Disk Partition Table,磁盘分区表), 64B
BRID(Boot Record ID,引导记录标识),2B,且必须是0x55AA
个人:在实际使用中,很多教材或文档以MBR指代Boot Sector,需要根据上下文区分
b. DPT分区表结构
DPT分区表的每个表项如上表所示,从中可以总结出,
① 每个表项占用16B,所以DPT最多只能容纳4个分区,因此传统磁盘上才引入了扩展分区和逻辑分区的概念
② 分区总扇区数为4B,即一个分区最多容纳2^32个扇区,即2TB
注意:这种分区表格式可以同时支持CHS和LBA地址模式的存储设备(LBA地址模式可以使用分区起始相对扇区号和分区总扇区数,同时将CHS相关的参数填写为最大值)
③ 函数流程
a. 获取SD卡信息并填充SDInfo结构
其中get_sd_block_count函数是通过读取/sys/block/sdb/size获得当前SD的block总数,总数为15976448 ≈ 7.62GB,而且 < (1023 * 254 * 63),所以是CHS_MODE
b. 划分FAT分区
首先空出SD卡的前10MB空间,然后设置该分区的2个属性,
bootable:设置为0,即非活动分区
partitionId = 0x0C,即W95 FAT32(LBA)分区
而该分区的大小则一直延伸到SD卡结束(以宏BLOCK_END标识),即将剩余空间全部划分为一个分区
注意:将剩余空间全部划分为一个分区时,会空出末尾的10MB空间
c. 构造分区表
分区表中仅有一个FAT32分区,从SD卡10MB处至SD卡末尾10MB处
注意:0x55AA为MBR标识
最终生成的分区表如下,符合上述分析,
C. C110-EVT-mkbl1.c文件分析
该文件与mkv210_image.c功能一致,用于读取uboot.bin的前8KB并计算其校验和,构成BL1
此处需要注意以下2点:
① uboot代码中预留BL1头部信息
在x210的uboot最前端,为BL1的头信息预留了16B,对照Header Info格式,此处指定的BL1为8KB
② 校验范围
BL1长度包含16B的头部信息,但是校验时并不包含头部信息
D. sd_fusing.sh脚本分析
./sd_fusing.sh /dev/sdb
① 参数验证
此处需要注意的是,-b用于判断$1标识的文件是否存在且是否是块设备
注意:x210提供的脚本有一处错误!!!
在定义变量partition的正确方式如下,
partition1="${1}1"
按原先脚本中partition1="$11"的定义,partition1的值为空,因为shell会认为11是一个变量,而11是没有定义的。
② 分区 + 格式化
说明1:如之前分析,调用sd_fdisk创建分区表,然后将分区表烧写到SD卡的第0扇区
说明2:通过 2> /dev/null重定位stderr是因为如果卸载不存在的分区,可以免去终端的错误打印信息
说明3:格式化/dev/sdb1分区时指定-F 32也是与分区时指定的FAT32格式匹配
③ 生成并烧写BL1
从uboot.bin开头截取8KB构造BL1(SD-bl1-8k.bin),然后烧写到SD卡第1扇区
④ 烧写BL2
将整个uboot.bin作为BL2烧写到SD的第49扇区(该扇区与uboot代码的拷贝位置匹配,详见后续文档)
E. 烧写验证
启动时先打印出了SD checksum Error,然后有uboot的启动信息,说明在iNand启动失败后,从SD卡的uboot启动成功
此时再验证一下SD卡中烧写的内容,需要注意的是导出SD扇区的命令,
# 获取MBR
sudo dd if=/dev/sdb of=0_sector.bin bs=512 count=1
MBR中只有DPT分区表和MBR标识
# 获取BL1的第1扇区
sudo dd if=/dev/sdb of=1_sector.bin skip=1 bs=512 count=1
这里需要特别注意的是skip选项(同时说明3个与读写数据相关的选项),
skip=BLOCKS:skip BLOCKS ibs-sized blocks at start of input
seek=BLOCKS:skip BLOCKS obs-sized blocks at start of output
bs=BYTES:read and write up to BYTES bytes at a time
ibs=BYTES:read up to BYTES bytes at a time (default: 512)
obs=BYTES:write BYTES bytes at a time (default: 512)
与写入SD不同,此处读取SD是input file需要偏移,所以应该使用skip选项
根据BL1前16B的头信息,BL1长度为8KB,且带有校验和
# 获取BL2的第1扇区
sudo dd if=/dev/sdb of=49_sector skip=49 bs=512 count=1
BL2的前16B就是代码中预留的头部信息位置,且没有改动
4. x210 iNand分区及uboot信息验证
1)iNand分区验证
x210分区情况如下,
可见iNand被划分为4个分区,且均为主分区(没有使用扩展分区),分区类型均为Linux分区(在实际使用中就对应了ext2/3/4的Linux系统默认的分区类型)。
/dev/mmcblk0p2为根分区,/dev/mmcblk0p4为用户分区,且这4个分区均非活动分区
对照iNand第0扇区中的内容与上述信息匹配(当然匹配,上述信息就是解析MBR得来的~~)。需要注意的是,fdisk -l显示信息中的Blocks是1K块的个数。
2)iNand如何分区
根据上文分析,SD卡的分区是sd_fdisk.c文件实现的,那么iNand的分区是谁实现的呢? 即iNand第0扇区的MBR信息从何而来?
A. fdisk命令
uboot中的fdisk命令可以创建/打印分区表
B. iNand分区流程
fdisk命令会调用do_fdisk函数实现,其中创建分区表会调用create_mmc_fdisk函数
与sd_fsik.c相同,构造分区表的方式就是构造MBR数组,并将该数组写入iNand的第0扇区
而构造分区表的方式则和之前sd_fdisk.c的方式完全相同(其实sd_fdisk.c就是移植了uboot的fdisk命令)
此处需要注意的是uboot中的分区安排(按iNand中先后顺序排列)
分区大小 |
分区用途 |
10MB |
空出iNand的前10MB空间(目前这部分有MBR、uboot[BL1 & BL2]、Linux kernel) |
256MB |
CONFIG_PART_SIZE(p1分区) |
120MB |
SYSTEM_PART_SIZE(p2分区) |
100MB |
CACHE_PART_SIZE(p3分区) |
剩余空间 |
用户空间(p4分区) |
说明:结合上文分析,p1和p3分区并未挂载,目前尚不知是否有其他组件使用
注意:system分区(p2分区)大小探讨
代码中的SYSTEM_PART_SIZE为120MB,但实际分区结果为256MB
该分区在Linux系统启动后的分区文件名为/dev/mmcblk0p2,存储的是根文件系统,在该分区烧写的文件系统镜像为rootfs_qt4.ext3,该文件大小为256MB,与目前的分区大小是匹配的。
导致这一问题的原因是x210提供的uboot.bin与源代码并不一致,目前源码并未同步官方镜像对分区表大小的修改~~
C. 使用GPT分区表的讨论
目前讨论的都是DPT分区表,如果要改用GPT分区表是否可行呢?
GPT分区表的结构如上图所示,扇区0需要写入Protective MBR(其中会设置一个类型未0xEE的分区),然后从第1到第34扇区写入GPT分区表,最后倒数33个扇区写入备份的GPT分区表。
但是根据S5PV210的启动流程,BL1必须放在第1扇区,因此就无法在此处部署GPT分区表。
因此是否可以使用GPT分区表,却决于SoC的启动方式,更准确的说是SoC从何SD/MMC的何处取得BL1/BL2
注意:之所以分区表要写入固定的位置,是因为Linux驱动会到该位置读取并解析分区表
3)uboot信息验证
关于iNand中uboot的信息,通过比较第1扇区和第49扇区的内容,二者完全相同。考虑到iNand中的uboot是通过fastboot或SD量产卡实现烧写的,而二者在烧写时使用的就是一份已加头检验的uboot.bin,所以推测是将同一份uboot.bin先截取8KB烧写到第1扇区,然后再将整个uboot.bin烧写到第49扇区(具体验证见fastboot原理分析文档)
5. uboot常用命令
1)帮助命令
help命令有2种使用方式,
A. 单独使用help
可以查看当前uboot支持的所有命令
B. help + 命令名
可以查看一个uboot命令的具体用法
2)环境变量类命令
A. 查看环境变量printenv
使用printenv可以查看当前uboot中的环境变量,print为该命令的简化版
B. 添加/修改/删除环境变量setenv
① 添加/修改
setenv name value
② 删除
setenv name
注意:设置含有特殊符号的环境变量值
如上文截图,uboot中的环境变量bootargs中包含分号,在设置时有2种处理方式
a. 使用单引号
b. 使用转义字符
C. 保存环境变量saveenv
在uboot启动过程中,会将Flash上存储的环境变量读入内存中,使用setenv修改的就是内存中的副本。
调用saveenv才会将当前定义的所有变量及其值写入Flash
说明:环境变量的保存是整体覆盖,即不能够单独保存某个环境变量的key-value
3)内存操作类命令
A. md(memory display)命令
md采用十六进制和ASCII码两种形式来显示内存的内容,其中b为单字节 / w为2字节 / l 为4字节,同时还可以指定一次显示的字节数。
说明:此处显示的地址为0x33e00000,对应uboot的链接地址(0xc3e00000),从内容对比可知uboot.bin确实被加载到此处
注意:uboot命令行中的所有数字默认作为十六进制处理
B. mw(memory write)命令
mw命令用于修改指定内存的内容
配合[count]字段还可以实现连续操作,下图即每次改写4B,连续修改16(0x10)次
C. mm(memory modify)命令
mm命令是一种互动修改内存的方法,他会显示地址和当前值,然后提示用户输入。如果输入一个合法的十六进制数,这个新值就会被写入该地址。然后地址自动递增,提示下一次修改。想终止修改时,只要输入空格,然后回车即可。
说明:此时选择的地址是uboot的链接地址
4)SD/MMC操作类命令
在x210的uboot中,使用movi命令族实现对iNand的操作
在环境变量bootcmd中就是使用
movi read kernel 30008000
将Linux kernel从iNand加载到内存中
补充:NandFlash操作命令
对于使用NandFlash的开发板,一般会提供另一套操作命令
#擦除start处开始的,长度为len的区域
nand erase 起始地址start 长度len
# 将内存地址起始处,长度为len的数据,写入flash起始地址处
nand write 内存起始地址 flash起始地址 长度len
# 将flash起始地址处,长度为len的数据,读到内存起始地址处
nand read 内存起始地址 flash起始地址 长度len
5)网络命令
A. 网络设置
如果要使用uboot的网络功能,需要正确设置如下的环境变量
ipaddr:开发板IP地址
gateway:开发板网关
netmask:子网掩码
serverip:开发板通过tftp命令下载文件时对应的tftp服务器地址
ethaddr:开发板网卡MAC地址(一般不用设置,除非与局域网中其他MAC地址冲突)
B. ping命令
uboot的ping命令一般不实现回显机制,所以都是用开发板ping主机
C. tftp命令
一般使用tftp命令从主机下载Linux kernel
6)启动内核命令
bootm命令的作用是执行固定格式的二进制文件,一般用于启动Linux内核,在启动内核时还可以传递initrd的地址,x210开发板启动内核时就附带了initrd的地址。
补充:uboot中的go命令也可以用于启动二进制文件,但是go命令只是简单修改PC指针跳转到指定地址执行,并不能传参
6. Linux启动参数解析
Linux内核启动时可以接收uboot给他传递的启动参数,这些参数由Linux内核规定。Linux内核在这些启动参数的指导下完成启动过程。这种设计使得可以在不重新编译内核的情况下,以不同的方式启动内核
下面分析一下x210的uboot传递给Linux内核的参数,
console=ttySAC2,115200:控制台使用串口2,波特率为115200
root=/dev/mmcblk0p2 rw:根文件系统在/dev/mmcblk0p2分区,以读写方式挂载
init=/linuxrc:此处需要使用绝对路径,用于指定init进程(默认运行/sbin/init进程)
rootfstype=ext3:根文件系统类型为ext3
Linux内核参数详细介绍可参考Linux内核源代码Documentation/kernel-parameters.txt
注意:启动过程中的一处错误
此处传递给Linux内核的参数中指定了根文件系统所在分区,但是在调用bootm启动内核时又指定了initrd在内存中的位置,而initrd一般在启动过程中作为虚拟根文件系统供Linux内核挂载
因此uboot的bootm命令实现中,会判断出该虚拟文件系统格式错误~~
此处增补一个疑问:uboot / Linux kernel / initrd在内存中的布局
根据相关文档分析,x210 uboot的链接地址为0xc3e00000,根据uboot中的MMU映射规则,该地址对应的物理地址为0x33e00000
但是bootcmd中将Linux kernel加载到0x30008000处,将initrd加载到0x30B00000处,这些内存地址之间会存在地址覆盖
但实际并没有出现问题,后续需要能够解释~~