面向Cortex-A8微处理器的U-boot移植与研究
2018-12-20姚茂群
周 力,姚茂群
(杭州师范大学,浙江 杭州 311121)
0 引 言
U-boot就是universal bootloader的简称,即通用的启动代码,在源代码级别具有移植性,可以针对多个开发板进行移植。U-boot起先是SourceForge的一个开源项目,由一个人发起,后由整个网络上所有感兴趣的人共同维护发展而来的一个bootloader。U-boot经过多年的发展,已经成为bootloader的标准,现在大部分的嵌入式设备都使用U-boot做为bootloader。U-boot的终极目的是启动内核,在U-boot中事先给Linux内核准备一些启动参数放在内存中,然后这些参数将被用来指导Linux内核的启动,借助U-boot可以部署整个Liunx系统,包括kernel、rootfs的镜像,也可以进行cpu级和板级硬件管理。当启动内核以后,U-boot的生命周期结束[1]。
1 U-boot移植准备
U-boot源代码是从U-boot官网下载的,由很多目录构成,一些主要目录的功能如表1所示。本次实验中使用的U-boot版本是U-boot官方的2013-10版本,在windows和ubuntu的共享文件下创建u-boot-2013-10目录,并从官网下载U-boot,解压到共享文件夹下,在ubuntu中也解压一份代码,在windows中修改代码,在ubuntu中编译代码,以方便整个实验进行[1-2]。
表1 U-boot主要源码目录与作用
2 U-boot启动过程
一个同时安装有bootloader、内核镜像和根文件系统镜像的固态存储设备的典型空间分配结构如图1所示。从固态存储设备上启动bootloader一般可以分为两个阶段,即阶段1和阶段2。阶段1为汇编阶段、阶段2为c语言阶段。阶段1注重SoC内部,在SRAM中完成,阶段2注重SoC外部,在DRAM中完成[2]。
图1 固态存储设备的典型空间分配结构
U-boot的第一阶段主要完成异常向量表构建,设置cpu为SVC模式,初始化时钟,重定位,加载U-boot第二阶段代码到DRAM等一些工作,具体步骤如图2所示。
图2 U-boot第一阶段
U-boot的第二阶段主要完成对开发板级别的硬件、软件数据结构进行初始化,包括网卡、控制台、SD卡、环境变量等的初始化[3]。具体步骤如图3所示。
图3 U-boot第二阶段
3 U-boot分析
U-boot本质上是一个C语言项目,由很多个项目组成,可以通过Makefile来做项目管理,方便编译链接。在进行移植时,U-boot中差别最大的是board和cpu这两个目录,这两个目录是和开发板有关的,在本次移植中使用的开发板cpu是三星公司的s5pv210,所以要找U-boot中针对s5pv210或者s5pc110的cpu进行移植的作为参考[4]。
3.1 U-boot主Makefile分析
根据移植的一般规律,参考include/configs/s5p_goni.h这个头文件,找到对应的board在board/samsung/goni这个目录下。
这个版本中的U-boot的Makefile使用了boards.cfg文件,因此在make xxx_config编译生成U-boot.bin的可执行二进制文件时,这个xxx要到boards.cfg文件中查找。经过分析为s5p_goni,配置时对应的cpu、board文件夹分别为:
cpu:u-boot-2013.10archarmcpuarmv7
board:u-boot-2013.10oardsamsunggoni
修改Makefile及CROSS_COMPILE:
由于使用的cpu是三星公司的s5pv210,属于Cortex-A8系列架构,所以它的ARCH属于arm系列,在ubuntu系统上找到交叉编译工具链安装的地方,复制相关路径到主Makefile中的CROSS_COMPILE,具体代码修改如下[5]:
ARCH=arm
CROSS_COMPILE=/usr/local/arm/arm-2009q3/bin//arm-none-linux-gnueabi-
3.2 mkconfig脚本分析
在U-boot的主Makeflie中存在以下两行代码:
%_config::unconfig
@$(MKCONFIG) -A $(@:_config=)
实际配置开发板时主Makefile传2个参数:-A和s5p_goni到mkconfig脚本中,在mkconfig脚本中使用awk正则表达式将boards.cfg文件中与刚才s5p_goni能够匹配上的那一行截取出来赋值给变量line,然后将line的内容以空格为间隔依次分开,分别赋值给$1,$2,…,$8。在解析完boards.cfg之后,$1到$8就有了新的值,将这些值用于开发板的配置,具体的值如下所示[5]:
$1=Active,$2=arm,$3=armv7,$4=s5pc1xx,$5=samsung,$6=goni,$7=s5p_goni,$8=-
4 U-boot移植
在C语言中整个项目的入口就是main函数,而在U-boot阶段由于有汇编阶段的参与,整个程序的入口取决于u-boot.lds中ENTRY声明的地方。因此start.S即是整个U-boot的入口。在start.S中通过一句跳转指令bl cpu_init_crit,短跳转到lowlevel_init函数。lowlevel_init函数中完成的工作有:关看门狗、调用uart_asm_init来初始化串口、初始化时钟。跳转出来以后在cpu_init_crit函数中初始化完DDR,从而进入到第二阶段,所以在这里执行重定位代码,其中的_TEXT_BASE是U-boot起始链接地址。U-boot的第二阶段就在crt0.S文件中,第二阶段的入口就是_main函数。第二阶段分成了board_init_f和board_init_r这两个函数,board_init_f函数主要是各种板级初始化。初始化控制台、时钟、DRAM等等。board_init_r函数主要是各种外设的初始化,如初始化iNand/SD、环境变量、网卡等等。在本次实验中移植和分析U-boot最主要的几个模块。
4.1 DDR移植
在s5pv210的核心板中有两块DDR内存,每块128 MB,总共256 MB。将三星版本的U-boot中的DDR内存初始化函数有关的cpu_init.S和头文件s5pc110.h复制到U-boot-2013-10/board/Samsung/goni下,这部分代码必须在程序运行代码前8 kb以内,否则校验会不通过,导致U-boot无法正常启动。在/board/Samsung/goni/Makefile中和/arch/arm/cpu/u-boot.lds中分别做相应修改,如下:
LOW:=lowlevel_init.o cpu_init.o
board/samsung/goni/lowlevel_init.o (.text*) board /samsung/goni/cpu_init.o (.text*)
下一步修改相应的DDR配置参数。将三星版本的include/configs/smdkv210single.h中的DDR相关参数复制到include/configs/s5p_goni.h下,查相关数据手册得,在s5pv210的核心板中MEMORY_BASE_ADDRESS为0x30000000[6],而在三星版本中MEMORY_BASE_ADDRESS为0x20000000,做如下修改:
#define MEMORY_BASE_ADDRESS 0x20000000改为:
#define MEMORY_BASE_ADDRESS 0x3000000
#define DMC0_MEMCONFIG_0 0x20F01323改为:
#define DMC0_MEMCONFIG_0 0x30F01323
4.2 重定位
从逻辑上来说,重定位部分代码应该在DDR初始化之后和U-boot第二阶段来临前之间。U-boot的第一阶段和第二阶段的划分并不是绝对的,唯一必须遵循的就是第一阶段代码不能大于8 KB。所以U-boot的第一阶段至少要完成DDR初始化和重定位。在满足这些条件之后,选择在u-boot-2013-10/board/samsung/goni/movi.c中完成重定位工作。将三星版本中的cpu/s5pc11x/movi.c和include/movi.h分别复制到u-boot-2013-10/board/samsung/goni和u-boot-2013-10/include下[6-7]。重定位完成以后,清理完bss段,数据段,设置好栈,用一句ldrpc, __main跳转到_main函数,这也是U-boot第一阶段和第二阶段的分界线。修改相应的makefile和u-boot.lds,做如下修改:
LOW:=movi.o
board/samsung/goni/movi.o (.text*)
4.3 SD卡驱动移植
4.3.1 SD卡驱动移植分析
SD卡驱动工作包含2部分内容,一部分是drivers/mmc目录下的驱动,另外一部分是U-boot自己提供的初始化代码,譬如GPIO初始化、时钟初始化等。在u-boot-2013-10中,驱动相关的文件主要有[7]:
drivers/mmc/mmc.c
drivers/mmc/sdhci.c
drivers/mmc/s5p_sdhci.c
board/samsung/goni/goni.c
在三星移植版本中,驱动相关的文件主要有:
drivers/mmc/mmc.c
drivers/mmc/s3c_hsmmc.c
cpu/s5pc11x/cpu.c
cpu/s5pc11x/setup_hsmmc.c
4.3.2 复制文件以及修改Makefile
将三星的drivers/mmc复制到u-boot-2013-10的drivers/mmc中,为了使原来三星中的sdhci.o和s5p_sdhci.o不发生编译错误,需要将drivers/mmc/Makefile中涉及到的相应宏在s5p_goni.h中注释掉,如下:
//#define CONFIG_SDHCI
//#define CONFIG_S5P_SDHCI
在drivers/mmc/Makefile中添加从三星复制过来的文件的编译项COBJS-$(CONFIG_S3C_HSMMC)+=s3c_hsmmc.,再在u-boot-2013-10/include/configs/s5p_goni.h中添加原来在三星定义的关于SD卡的宏,主要如下[8]:
#define CONFIG_S3C_HSMMC
#define SDMMC_BLK_SIZE (0xD003A500)
#define COPY_SDMMC_TO_MEM (0xD003E008)
#define USE_MMC0
#define USE_MMC2
#define MMC_MAX_CHANNEL4
4.3.3 驱动相关移植
对比两个U-boot,找到u-boot-2013-10中没有定义的函数的头文件所在路径,将三星版本的include目录下的mmc.h和s3c_hsmmc.h移植到u-boot-2013-10中include目录下。将三星的drivers/mmc.c中的cpu_mmc_init函数复制到board/samung/goni中board_mmc_init函数中,移植三星的cpu/s5pc11x/setup_hsmmc.c到board/Samsung/goni下,在goni目录下的Makefile中添加COBJS-y:=goni.o setup_hsmmc.o。
在本次实验中用SD卡从ubuntu中拷贝程序,整个U-boot能成功启动,即代表SD卡驱动移植成功。
4.4 环境变量的移植
4.4.1 环境变量理解
环境变量有2份,一份在Flash中,另一份在DDR中。U-boot开机时一次性从Flash中读取全部环境变量到DDR中作为环境变量的初始化值,使用过程中都是用DDR中这一份,可以用saveenv指令将DDR中的环境变量重新写入Flash中去更新Flash中环境变量。下次开机时又会从Flash中再读取一次[8]。
4.4.2 环境变量保存位置分析
U-boot烧录时使用的扇区数是SD卡的扇区1-16和49-x(x-49大于等于U-boot的大小)。从U-boot的烧录情况来看,SD卡的扇区0空闲,扇区1-16被U-boot的BL1占用,扇区17-48空闲,扇区49-x被U-boot的阶段二占用。再往后就是kernel、rootfs等镜像的分区了。所以ENV不能往扇区1-16或者49-x中来放置,其他地方都可以。ENV的大小是16 K字节也就是32个扇区。在环境变量移植中宏CONFIG_ENV_OFFSET决定了环境变量在SD卡中相对SD卡扇区0的偏移量,也就是ENV写到SD卡的哪里去了。修改这个CONFIG_ENV_OFFSET的值,将ENV写到从第17扇区开始的地方,具体如下:
#define CONFIG_ENV_OFFSET 17*512 //第17扇区开始的位置
#define MOVI_BL2_POS ((eFUSE_SIZE/MOVI_BLKSIZE)+MOVI_BL1_BLKCNT+ MOVI_ENV_BLKCNT)
宏MOVI_BL2_POS决定了U-boot的BL2开始扇区,宏(eFUSE_SIZE/MOVI_BLKSIZE)为1,代表扇区0,宏MOVI_BL1_BLKCNT为16,存放U-boot的BL1,宏MOVI_ENV_BLKCNT为32,存放ENV,从扇区49开始就是U-boot的BL2了。为了实现环境变量不冲突,将ENV放到17扇区起始的地方即可。
4.4.3 环境变量的测试
程序修改重新编译后启动,为了确保iNand里面没有之前保存过的环境变量,对iNand的前49个扇区进行擦除。使用命令:mmc write 0 30000000 0# 49来擦除SD卡的扇区0-48,这样以前的环境变量都没有了。重新开机后先修改bootdelays等于3作为标记后saveenv保存环境变量后重启。测试方法是,使用命令mmc read 0 30000000 17# 32将iNand的17开始的32个扇区读出来到内存30000000处,用md命令查看。找到显示区域里面的各个环境变量,看到读出来的值与刚才标记的值一样,说明修改成功[9]。具体如图4所示。
图4 环境变量测试效果
4.5 网卡移植
4.5.1 网卡移植分析
U-boot中对各种功能的实现是一种可配置可裁剪的设计,默认情况下U-boot没有选择支持网络。当在配置头文件中添加一行#define CONFIG_CMD_NET,即添加了网络支持宏之后,U-boot的board_init_r函数中初始化时就会执行eth_initialize函数,从而网络相关代码初始化就会被执行,将来网络就可以使用[10]。
4.5.2 设置网络相关的环境变量
#define CONFIG_ETHADDR00 40:5c:26:0a:5b
#define CONFIG_NETMASK 255.255.255.0 //子网掩码
#define CONFIG_IPADDR 172.17.64.188 //开发板的地址
#define CONFIG_SERVERIP 172.17.64.167 //虚拟机IP地址
#define CONFIG_GATEWAYIP 172.17.64.1 //网关
4.5.3 添加ping和tftp命令
在Linux系统中网络底层驱动被上层应用调用的接口是socket,是一个典型的分层结构,底层和上层是完全被socket接口隔离的。但是在U-boot中网络底层驱动和上层应用是不分层的[11-12]。即上层网络的每一个应用都是自己去调用底层驱动中的操作硬件的代码来实现的。U-boot中有很多预先设计的需要用到网络的命令,直接相关的就是ping和tftp这两个命令。这两个命令在U-boot中也是需要用相应的宏开关来打开或者关闭的。
经分析,发现ping命令开关宏为CONFIG_CMD_PING,而tftp命令的开关为CONFIG_CMD_NET,已经存在。所以只需在include/configs/S5p_goni.h中添加ping命令相关宏定义即可,如下:
#define CONFIG_CMD_PING
4.5.4 移植及注册网卡驱动
为了让网卡初始化函数dm9000_pre_init在u-boot-2013-10/board/samsung/goni.c中的board_init函数下调用,添加dm9000_pre_init函数到goni.c下,将网卡相关的宏添加到s5p_goni.h下,并且添加上相应的头文件。添加的宏如下:
#define CONFIG_DRIVER_DM9000 1
#define CONFIG_DM9000_BASE(0x88000300) //bank1基地址
#define DM9000_IO(CONFIG_DM9000_BASE) //IO基地址
#define DM9000_DATA(CONFIG_DM9000_BASE+4) //ADD2加4为了得到地址
在Linux的网卡驱动体系中,有一个数据结构(struct eth_device)用来封装一个网卡的所有信息,系统中注册一个网卡时就是要建立一个这个结构体的实例,然后填充这个实例中的各个元素,最后将这个结构体实例加入到eth_devices链表上,就完成了注册[13]。在/drivers/net/dm9000x.c中的最后一个函数int dm9000_initialize(bd_t *bis),就是用来注册dm9000网卡驱动的。自己定义一个board_eth_init函数用来做网卡驱动添加工作,即可以注册驱动成功。在本次实验中能使用tftp加载内核最终启动Linux系统则说明网卡驱动移植成功。
4.6 实验结果
当U-boot移植完以后,需要通过设置tftp加载ubuntu中编译生成的内核镜像和用nfs的方式远程挂载ubuntu中制作好的根文件系统。配置好tftp和nfs以后,设置U-boot的bootargs,如根目录所在路径,主机IP,开发板IP,网关,串口,波特率等以支持nfs方式挂载rootfs,具体设置如下[13-14]:
setenv bootargs root=/dev/nfs
nfsroot=192.168.1.141:/root/porting_x210/rootfs
ip=172.17.64.188:172.17.64.167:172.17.64.1:255.255.255.0::eth0:off
init=/linuxrc console=ttySAC2,115200
设置完成后,成功加载内核和根文件系统,整个Linux系统启动起来。成功启动U-boot如图5所示,成功启动Linux系统如图6所示。
图5 U-boot成功启动
图6 Linux系统成功启动
5 结束语
在Linux系统中U-boot是必不可少的一部分,完成了包括cpu级和板级必要硬件设备的初始化,为操作系统的启动提供了必要的条件和参数。在本次移植过程中,详细阐述了U-boot的启动过程,分析了U-boot的主Makefile和mkconfig,介绍了U-boot移植中主要几个移植对象的移植方法。实验结果表明,将官方U-boot移植到基于s5pv210的x210的开发板上可行,同时通过tftp的方式加载内核和nfs方式挂载启动ext2的根文件系统,从而启动整个Linux系统,为以后嵌入式系统开发提供必要环境。