410 likes | 640 Views
第五章 bootloader 移植. 主要内容. 5.1 Bootloader 5.1.1 Bootloader 介绍 5.1.2 Bootloader 的种类 5.1.3 Bootloader 的操作模式 5.1.4 Bootloader 的启动流程 5.1.5 BootLoader 与主机之间的通信协议 5.1.6 关于串口. 主要内容 ( 续 ). 5.2 U-Boot 的移植 5.2.1 U-Boot 工程简介 5.2.2 U-Boot 源码结构 5.2.3 U-Boot 的移植 U-boot 启动流程分析 U-boot 源码修改
E N D
主要内容 • 5.1 Bootloader • 5.1.1 Bootloader介绍 • 5.1.2 Bootloader的种类 • 5.1.3 Bootloader的操作模式 • 5.1.4 Bootloader的启动流程 • 5.1.5 BootLoader 与主机之间的通信协议 • 5.1.6 关于串口
主要内容(续) • 5.2 U-Boot的移植 • 5.2.1 U-Boot工程简介 • 5.2.2 U-Boot源码结构 • 5.2.3 U-Boot的移植 • U-boot启动流程分析 • U-boot源码修改 • U-boot的编译 • U-boot的烧写 • U-boot的启动 • 5.3 U-Boot常用命令 • 5.3 U-Boot的环境变量
5.1.1 Bootloader介绍 • 在专用的嵌入式板子运行 GNU/Linux 系统已经变得越来越流行。一个嵌入式 Linux 系统从软件的角度看通常可以分为四个层次: • 1. 引导加载程序。即 Boot Loader 。 • 2. Linux 内核。特定于嵌入式板子的定制内核以及内核的启动参数。 • 3. 文件系统。包括根文件系统和建立于 Flash 内存设备之上文件系统。通常用 ram disk 来作为 root fs。 • 4. 用户应用程序。特定于用户的应用程序。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式 GUI 有:QT、MicroWindows 、MiniGUI 等。
5.1.1 Bootloader介绍 • Bootloader,称为引导加载程序,是嵌入式系统加电后运行的第一段代码,相当于PC机的BIOS • Bootloader通常固化在硬件上某个固态存储设备上,加电后自启动。 • 通过Bootloader这段代码,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
Bootloader介绍 • 简单地说,Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。 • 通常,Boot Loader 是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的 Boot Loader 几乎是不可能的。尽管如此,我们仍然可以对 Boot Loader 归纳出一些通用的概念来,以指导用户特定的 Boot Loader 设计与实现。
Boot Loader 的安装媒介(Installation Medium) • 系统加电或复位后,所有的 CPU 通常都从某个由 CPU 制造商预先安排的地址上取指令。比如,基于 ARM7TDMI core 的 CPU 在复位时通常都从地址 0x00000000 取它的第一条指令。而基于 CPU 构建的嵌入式系统通常都有某种类型的固态存储设备(比如:ROM、EEPROM 或 FLASH 等)被映射到这个预先安排的地址上。因此在系统加电后,CPU 将首先执行 Boot Loader 程序。 • 下图就是一个同时装有 Boot Loader、内核的启动参数、内核映像和根文件系统映像的固态存储设备的典型空间分配结构图。
5.1.2 Bootloader的种类 • 1、vivi vivi是韩国Mizi公司开发的Bootloader,适用于ARM9处理器。 • 2、RedBoot RedBoot也称作红帽(Red Hat)嵌入式调试引导程序,是一种用于嵌入式系统的独立开放源代码引导/装载器。 • 3、U-Boot U-Boot(Universal Bootloader)是一款目前功能较为强大的开源Bootloader程序,它支持多种处理器平台,包括ARM、MIPS等。
5.1.3 Bootloader的操作模式 • 大多数Bootloader都包含两种不同的操作模式:“启动加载”模式和“下载”模式,这种区别仅对于开发人员才有意义。 • •从最终用户的角度看,Bootloader的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。
1、启动加载(Bootloading)模式 • 启动加载模式称为“自举”(Autonomous)模式。即Bootloader从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。 • 启动加载模式是 Bootloader的正常工作模式,在嵌入式产品发布的时侯,Bootloader必须工作在这种模式下。
2、下载(Downloading)模式 • 在这种模式下,目标机上的Bootloader将通过串口连接或网络连接等通信手段从主机上下载文件。 比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被Bootloader保存到目标机的RAM中,然后再被 Bootloader写到目标机上的FLASH 类固态存储设备中。 • Bootloader的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用到这种工作模式。 • 工作于这种模式下的Bootloader通常都会向它的终端用户提供一个简单的命令行接口。 如在RedBoot下,将出现“RedBoot>”提示符。
2、下载(Downloading)模式 • 像RedBoot或U-Boot等功能强大的Bootloader通常都可同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。 比如,RedBoot在启动时处于正常的启动加载模式,但是它会延时3秒等待终端用户按下任意键而将RedBoot切换到下载模式。如在等待时间内没有接收到用户按键,则继续启动 Linux 内核。
5.1.4 Bootloader的启动流程 • Bootloader的启动通常可以分为stage1和stage2两个阶段。
1、stage1阶段 • stage1主要包含依赖于CPU的体系结构,比如设备初始化代码等。通常都用汇编语言来实现。这个阶段的任务有: • •(1)、基本的硬件设备初始化。 • •这是 Boot Loader 一开始就执行的操作,其目的是为 stage2 的执行以及随后kernel 的执行准备好一些基本的硬件环境。 • 它通常包括以下步骤:屏蔽所有的中断(为中断提供服务通常是操作系统设备驱动程序的责任,因此在Bootloader 的执行全过程中可以不必响应任何中断。)、设置 CPU 的速度和时钟频率、关闭处理器内部指令/数据cache(通常使用内部指令和cache可以提高系统性能,但由于cache的使用可能改变访问主存的数量、类型和时间,因此Bootloader的执行通常不需要。)等。
1、stage1阶段(续) • (2)、为stage2准备包括RAM空间。 • •为了获得更快的执行速度,通常把 stage2 加载到 RAM 空间中来执行,因此必须为加载 Boot Loader 的 stage2 准备好一段可用的 RAM 空间范围。 • •由于stage2通常是C语言执行代码,因此在考虑空间大小时,除了stage2可执行映像的大小外,还必须把堆栈空间也考虑进来。此外,空间大小最好是 memory page 大小(通常是4KB)的倍数。一般而言,1M的RAM空间已经足够了。具体的地址范围可以任意安排,但是,将stage2安排到整个RAM空间的最顶1M是一种值得推荐的方法。此外,还必须确保所安排的地址范围确实是可读写的RAM空间。
1、stage1阶段(续) • •为了后面的叙述方便,这里把RAM 空间范围的大小记为:stage2_size(字节),把起始地址和终止地址分别记为:stage2_start 和 stage2_end(这两个地址均以 4 字节边界对齐)。因此,stage2_end=stage2_start+stage2_size。 • (3)、拷贝stage2到RAM空间。 • •(4)、设置好堆栈。 • •堆栈指针的设置是为了执行 C 语言代码作好准备。通常我们可以把 sp 的值设置为(stage2_end-4),也即前面所提到的1MB 的 RAM 空间的最顶端(堆栈向下生长)。 • •(5)、跳转到stage2的C入口点。 • 在上述一切都就绪后,就可以跳转到 Boot Loader 的 stage2 去执行了。
2、stage2阶段 • stage2通常用C语言来实现,以便实现更复杂的功能,也使程序有更好的可读性和可移植性。这个阶段的主要任务有: • (1)、初始化本阶段要使用到的硬件。 • •这通常包括:初始化至少一个串口,以便和终端用户进行 I/O 输出信息等。
2、stage2阶段(续) • (2)、检测系统内存映射(memory map)。 • •所谓内存映射就是指在整个物理地址空间中有哪些地址范围被分配用来作为寻址系统的 RAM 单元。虽然 CPU 通常预留出一大段足够的地址空间给系统 RAM,但是在搭建具体的嵌入式系统时却不一定会实现 CPU 预留的全部 RAM 地址空间。也就是说,具体的嵌入式系统往往只把 CPU 预留的全部 RAM 地址空间中的一部分映射到 RAM 单元上,而让剩下的那部分预留 RAM 地址空间处于未使用状态。 • • 基于上述这个事实,因此 Boot Loader 的 stage2 必须在它想干点什么 (比如,将存储在 flash 上的内核映像读到 RAM 空间中) 之前检测整个系统的内存映射情况,也即它必须知道 CPU 预留的全部 RAM 地址空间中的哪些被真正映射到 RAM 地址单元,哪些是处于 "unused" 状态的。
2、stage2阶段(续) • (3)、将kernel和根文件系统映像从flash上读到RAM空间中。 • •由于像 ARM 这样的嵌入式 CPU 通常都是在统一的内存地址空间中寻址 Flash 等固态存储设备的,因此从 Flash 上读取数据与从 RAM 单元中读取数据并没有什么不同。这一步骤包括两部分内容:规划kernel和根文件系统所占用的内存范围和将它们从flash上进行拷贝。
2、stage2阶段(续) • (4)、为kernel设置启动参数。 • •这是在调用内核之前应该做的准备工作。Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 开始,以标记 ATAG_NONE 结束。在嵌入式 Linux 系统中,通常需要由 Boot Loader 设置的常见启动参数有:ATAG_CORE、ATAG_MEM(内存映射)、ATAG_NONE等。
2、stage2阶段(续) • (5)、调用内核。 • •Bootloader调用Linux kernel的方法是直接跳转到内核的第一条指令处。在跳转时必须满足下列条件。 • •(1)、CPU寄存器的设置:R0为0;R1为机器类型ID(机器类型参见 linux/arch/arm/tools/mach-types目录);R2为启动参数,标记列表在RAM中的起始基地址。 • •(2)、CPU模式:必须禁止中断(IRQs和FIQs);CPU必须设置为SVC模式。 • •(3)、Cache 和 MMU 的设置:MMU 必须关闭;指令 Cache 可以打开也可以关闭; 数据 Cache 必须关闭。
5.1.5 BootLoader 与主机之间的通信协议 • 最常见的情况就是,目标机上的 Boot Loader 通过串口与主机之间进行文件传输,传输协议通常是 xmodem/ymodem/zmodem 协议中的一种。但是,串口传输的速度是有限的,因此通过以太网连接并借助 TFTP 协议来下载文件是个更好的选择。 • 此外,在论及这个话题时,主机方所用的软件也要考虑。比如,在通过以太网连接和 TFTP 协议来下载文件时,主机方必须有一个软件用来的提供 TFTP 服务。
5.1.6 关于串口终端 • 主机和目标机之间一般通过串口建立连接,Boot Loader 软件在执行时通常会通过串口来进行 I/O,比如:输出打印信息到串口,从串口读取用户控制字符等。 • 在 boot loader 程序的设计与实现中,没有什么能够比从串口终端正确地收到打印信息能更令人激动了。此外,向串口终端打印信息也是一个非常重要而又有效的调试手段。但是,我们经常会碰到串口终端显示乱码或根本没有显示的问题。 • 造成这个问题主要有两种原因:(1) boot loader 对串口的初始化设置不正确。(2) 运行在 host 端的终端仿真程序对串口的设置不正确,这包括:波特率、奇偶校验、数据位和停止位等方面的设置。
关于串口终端(续) • 此外,有时也会碰到这样的问题,那就是:在 boot loader 的运行过程中我们可以正确地向串口终端输出信息,但当 boot loader 启动内核后却无法看到内核的启动输出信息。对这一问题的原因可以从以下几个方面来考虑: • (1) 首先请确认你的内核在编译时配置了对串口终端的支持,并配置了正确的串口驱动程序。 • (2) 你的 boot loader 对串口的初始化设置可能会和内核对串口的初始化设置不一致。此外,对于诸如 s3c44b0x 这样的 CPU,CPU 时钟频率的设置也会影响串口,因此如果 boot loader 和内核对其 CPU 时钟频率的设置不一致,也会使串口终端无法正确显示信息。 • (3) 最后,还要确认 boot loader 所用的内核基地址必须和内核映像在编译时所用的运行基地址一致,尤其是对于 uClinux 而言。假设你的内核映像在编译时用的基地址是 0xc0008000,但你的 boot loader 却将它加载到 0xc0010000 处去执行,那么内核映像当然不能正确地执行了。
5.2 U-Boot的移植 • 5.2.1 U-Boot工程简介 • 5.2.2 U-Boot源码结构 • 5.2.3 U-Boot的编译
5.2.1 U-Boot工程简介 • 最早,DENX软件工程中心的Wolfgang Denk基于8xxrom的源码创建了PPCBOOT工程,并且不断添加处理器的支持。后来,Sysgo Gmbh把ppcboot移植到ARM平台上,创建了ARMboot工程。然后以ppcboot工程和armboot工程为基础,创建了U-Boot工程。 • 现在U-Boot已经能够支持PowerPC、ARM、X86、MIPS体系结构的上百种开发板,已经成为功能最多、灵活性最强并且开发最积极的开放源码Bootloader。目前仍然由DENX的Wolfgang Denk维护。 • U-Boot的源码包可以从sourceforge网站下载 • U-Boot软件包下载网站:http://sourceforge.net/project/u-boot
U-boot特点 • 1 开放源码 • 2 支持多种嵌入式操作系统内核,如Linux、Vxworks、QNX等 • 3 支持多个处理器系列,如PowerPC、ARM、X86、MIPS、XScale; • 4 较高的可靠性和稳定性 • 5 高度灵活的功能设置,适合U-BOOT调试,操作系统不同引导要求,产品发布等 • 6 丰富的设备驱动源码,如串口、以太网、SDRAM、FLASH、LCD、EEPROM、RTC、键盘等; • 7 较丰富的开发调试文档与强大的网络技术支持
U-boot主要功能 • 1 系统引导:支持NFS挂载、RAMDISK形式的根文件系统。支持NFS挂载并从FLASH中引导内核 • 2 基本辅助功能:强大的操作系统接口功能;可灵活设置、传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与产品发布,尤其对Linux支持最为强劲;支持目标板环境参数多种存储方式,可校验FLASH中内核、RAMDISK镜像文件是否完好。 • 3 设备驱动:串口、SDRAM、FLASH、以太网、LCD、EEPROM、键盘、USB、PCMCIA、PCI、RTC等驱动支持 • 4 上电自检功能:SDRAM、FLASH大小自动检测;SDRAM故障检测;CPU型号等 • 5 其他功能
5.2.2 U-Boot源码结构 • 解压就可以得到全部U-Boot源程序。在顶层目录下有18个子目录,分别存放和管理不同的源程序。这些目录中所要存放的文件有其规则,可以分为3类。 • · 第1类目录与处理器体系结构或者开发板硬件直接相关; • · 第2类目录是一些通用的函数或者驱动程序; • · 第3类目录是U-Boot的应用程序、工具或者文档。
5.2.3 U-Boot的源码分析 U-boot顶层目录的Makefile • U-Boot的源码是通过GCC和Makefile组织编译的。顶层目录下的Makefile首先可以设置开发板的定义,然后递归地调用各级子目录下的Makefile,最后把编译过的程序链接成U-Boot映像。
1.顶层目录下的Makefile • 它负责U-Boot整体配置编译。按照配置的顺序阅读其中关键的几行。 • 每一种开发板在Makefile都需要有板子配置的定义。例如smdk2410开发板的定义如下。 • smdk2410_config : unconfig • @./mkconfig $(@:_config=) arm arm920t smdk2410 NULL s3c24x0 • 执行配置U-Boot的命令make smdk2410_config ,通过./mkconfig脚本生成include/config.mk的配置文件。文件内容正是根据Makefile对开发板的配置生成的。
顶层目录下的Makefile • ARCH = arm • CPU = arm920t • BOARD = smdk2410 • SOC = s3c24x0 • 上面的include/config.mk文件定义了ARCH、CPU、BOARD、SOC这些变量。这样硬件平台依赖的目录文件可以根据这些定义来确定。SMDK2410平台相关目录如下。 • board/smdk2410/ • cpu/arm920t/ • cpu/arm920t/s3c24x0/ • lib_arm/ • include/asm-arm/ • include/configs/smdk2410.h
顶层目录下的Makefile • Makefile的编译选项和规则在顶层目录的config.mk文件中定义。各种体系结构通用的规则直接在这个文件中定义。通过ARCH、CPU、BOARD、SOC等变量为不同硬件平台定义不同选项。不同体系结构的规则分别包含在ppc_config.mk、arm_config.mk、mips_config.mk等文件中。 • 顶层目录的Makefile中还要定义交叉编译器,以及编译U-Boot所依赖的目标文件。
顶层目录下的Makefile • ifeq ($(ARCH),arm) • CROSS_COMPILE = arm-linux- //交叉编译器的前缀 • ……. • # U-Boot objects....order is important (i.e. start must be first) • OBJS = cpu/$(CPU)/start.o //处理器相关的目标文件 • …….. • LIBS = lib_generic/libgeneric.a //定义依赖的目录,每个目录下先把目标文件连接成*.a文件。 • LIBS += board/$(BOARDDIR)/lib$(BOARD).a • LIBS += cpu/$(CPU)/lib$(CPU).a • ifdef SOC • LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a • endif • LIBS += lib_$(ARCH)/lib$(ARCH).a
然后还有U-Boot映像编译的依赖关系 • ALL = u-boot.srec u-boot.bin System.map • all: $(ALL) • u-boot.srec: u-boot • $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@ • u-boot.bin: u-boot • $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@ • …… • u-boot: depend $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT) • UNDEF_SYM='$(OBJDUMP) -x $(LIBS) \ • |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\ • $(LD) $(LDFLAGS) $$UNDEF_SYM $(OBJS) \ • --start-group $(LIBS) $(PLATFORM_LIBS) --end-group \ • -Map u-boot.map -o u-boot
顶层目录下的Makefile • Makefile缺省的编译目标为all,包括u-boot.srec、u-boot.bin、System.map。u-boot.srec和u-boot.bin又依赖于U-Boot。U-Boot就是通过ld命令按照u-boot.map地址表把目标文件组装成u-boot。
编译结果 • 根据对Makefile的分析,编译分为2步。第1步配置,例如:make smdk2410_config;第2步编译,执行make就可以了。 • 编译完成后,可以得到U-Boot各种格式的映像文件和符号表 ,如下: • System.map U-Boot映像的符号表 • u-boot U-Boot映像的ELF格式 • u-boot.srec U-Boot映像的S-Record格式 • u-boot.bin U-Boot映像原始的二进制格式 • U-Boot的3种映像格式都可以烧写到Flash中,但需要看加载器能否识别这些格式。一般u-boot.bin最为常用,直接按照二进制格式下载,并且按照绝对地址烧写到Flash中就可以了。 U-Boot和u-boot.srec格式映像都自带定位信息。