本文转载:http://blog.ednchina.com/hhuwxf/1915416/message.aspx
一、Bootloader的引入
从之前的硬件实验可以看出,系统上电后,需要一个程序来初始化:关闭WATCHDOG、改变系统时钟、初始存储控制器、将更多代码复制到中等内存等。如果能将操作系统内核(无论是本地的,比如Flash;还是从远端,比如通过网络)复制到内存中运行,就称这段程序为Bootloader。
简单地说,Bootloader这么短的程序,它在系统上电时就开始执行,初始化硬件设备,准备好软件环境,最后调用操作系统内核。
可以增强Bootloader比如增加网络功能,从PC通过串口或网络下载文件,烧写文件,将Flash上压缩文件解压后再运行等──这是一个更强大的功能Bootloader,也称为Monitor。事实上,用户在最终产品中不需要这些功能,只是为了方便开发。
Bootloader在嵌入式系统中,硬件配置非常不同,即使是相同的CPU,它的外设(例如Flash)也可能不同,所以不可能有一个Bootloader支持所有的CPU、所有电路板。甚至支持CPU架构比较多的U-Boot,不能一拿就用(除非里面的配置和你的板子一样),需要一些移植。
二、 Bootloader的启动方式
CPU上电后,将从某个地址执行。MIPS结构的CPU会从0xBFC00000取第一条指令,而ARM结构的CPU则从地址0x从一万开始。嵌入式单板需要存储器件ROM或Flash等待映射到这个地方址,Bootloader将其存储在此地址的开头,以便一上电就能执行。
在开发过程中,通常需要使用各种命令Bootloader,一般通过串口连接PC和开发板,可以在串口上输入各种命令,观察操作结果等。这对开发人员来说只是有意义的,用户在使用产品时不需要接口来控制Bootloader的。从这个观点来看,Bootloader可分为两种操作模式(Operation Mode):
(1)启动加载(Boot loading)模式。
上电后,Bootloader操作系统从板上的固态存储设备加载到RAM在运行过程中,用户没有干预整个过程。产品发布时,Bootloader在这种模式下工作。
(2)下载(Downloading)模式。
在这种模式下,开发人员可以通过串口连接或网络连接从主机使用各种命令(Host)下载文件(如内核图像、文件系统图像),直接放入内存操作或烧入Flash类固态存储设备。
板与主机之间传输文件时,可使用串口xmodem/ymodem/zmodem协议使用简单,但速度慢;也可以通过网络使用网络tftp、nfs协议传输时,主机应打开tftp、nfs服务;还有其他方法,比如USB等。
像Blob或U-Boot等等功能强大Bootloader这两种工作模式通常同时支持允许用户在这两种工作模式之间切换。U-Boot启动时正常启动加载模式,但会延迟几秒钟(可以设置)等待终端用户按下任意键而将U-Boot切换到下载模式。如果在指定时间内没有用户按钮,则U-Boot继续启动Linux内核。编辑] 15.1.2 Bootloader结构及启动过程
- 1. 概述
移植前先了解Bootloader一些通用概念有助于理解它的代码。
嵌入式Linux从软件的角度来看,系统通常可以分为四个层次:
(1)引导加载程序,包括固化在固件(firmware)中的 boot 代码(可选)和Bootloader两大部分。
有些CPU在运行Bootloader固化程序(固件,firmware),比如x86结构的CPU就是先运行BIOS硬盘的第一个分区在运行之前是固件(MBR)中的Bootloader。
大多数嵌入式系统中没有固件,Bootloader是上电后执行的第一个程序。
(2)Linux内核。
嵌入式板的定制内核和内核的启动参数。内核的启动参数可以是默认的,也可以是默认的Bootloader传递给它的。
(3)文件系统。
包括根文件系统和根文件系统Flash内存设备之上的文件系统。里面包含了Linux系统可以操作所需的应用程序、库等,如为用户提供操作Linux控制界面shell动态连接程序运行所需的程序glibc或uClibc库,等等。
(4)用户应用程序。
特定于用户的应用程序也存储在文件系统中。有时,嵌入式图形用户界面可能包含在用户应用程序和核心层之间。常用的嵌入式 GUI 有:Qtopia 和 MiniGUI 等。
显然,嵌入系统的固态存储设备有相应的分区存储,图15.一是典型的分区结构。 [[Image:]]
图15.1 嵌入式Linux典型的分区结构在系统中
“Bootparameters一些可设置的参数存储在分区中,例如IP要传递给核心的地址、串口波特率、命令行参数等。正常启动时,Bootloader首先运行,然后将内核复制到内存中(一些内核可以直接在固态存储设备上运行),并在内存的固定地址设置传输到内核的参数,最后运行内核。内核启动移动后,它会挂接(mount)根文件系统(Root filesystem)启动文件系统中的应用程序。
- 2. Bootloader的两个阶段
Bootloader启动过程的启动过程可分为单阶段(SingleStage)、多阶段(Multi-Stage)两种。通常是多阶段的Bootloader它可以提供更复杂的功能和更好的可移植性。启动固态存储设备动的Bootloader大多都是 2 阶段的启动过程。这可以从之前的硬件实验中很好地理解:第一阶段是通过汇编来实现的,它完成了一些依赖 CPU系统结构的初始化,并在第二阶段调用代码。C语言通常用于第二阶段,以实现更复杂的功能,代码具有更好的可读性和可移植性。
一般来说,这两个阶段的功能可以分类如下,但这不是绝对的:
(1)Bootloader功能的第一阶段。
-
- 硬件设备的初始化。
- 为加载Bootloader代码准备的第二阶段RAM空间。
- 拷贝Bootloader代码的第二阶段 RAM 空间中。
- 设置好栈。
- 跳转到第二阶段代码的C入口点。
在第一阶段进行的硬件初始化一般包括:关闭WATCHDOG、关中断,设置CPU速度和时钟频率,RAM初始化等。这些都不是必须的,比如S3C2410/S3C2440开发板使用U-Boot中,就将CPU在第二阶段设置速度和时钟频率。
甚至在第二阶段复制代码RAM空间不是必要的,对NOR Flash等待存储设备,可以直接在上面执行代码,但与此相比RAM执行效率大大降低。
(2)Bootloader功能的第二阶段。
-
- 初始化本阶段要使用到的硬件设备。
- 内存映射检测系统(memory map)。
- 内核图像和根文件系统图像Flash上读到RAM空间中。
- 为内核设置启动参数。
- 调用内核。
为了方便开发,程序员和Bootloader进行交互。
所谓检测内存映射,就是确定板上使用了多少内存,它们的地址空间是什么。由于嵌入式开发,Bootloader它主要是为某种板编写的,因此可以根据板的情况直接设置,不需要考虑适用于各种情况的复杂算法。
Flash上面的内核图像可能被压缩并读取RAM之后需要解压。当然,对于具有自解压功能的核心,不需要Bootloader来解压。
复制根文件系统的图像RAM这是不必要的。这取决于根文件系统的类型,以及内核访问它的方法。
下一节将介绍内核设置的启动参数。
将内核存放在适当的位置后,直接跳到其入口点调用内核。调用内核前,应满足以下条件:
(1)CPU 设置寄存器。
-
- R0=0
- R1=机器类型ID;对于ARM结构的CPU,其机器类型ID可以参见 linux/arch/arm/tools/mach-types。
- R2=启动参数标记表 RAM 中始基地址
(2)CPU工作模式。
-
- 禁止中断(IRQs和FIQs)
- CPU 必须 SVC 模式
(3)Cace 和 MMU 的设置。
-
- MMU 必须关闭
- 指令 Cache 可以打开也可以关闭
- 数据 Cache 必须关闭
如果用C语言,可以像下列示例代码一样来调用内核:
void (*theKernel)(int zero, int arch, u32 params_addr) = (void (*)(int, int, u32))KERNEL_RAM_BASE; …… theKernel(0, ARCH_NUMBER, (u32) kernel_params_start);
- 3. Bootloader与内核的交互
Bootloader与内核的交互是单向的,Bootloader将各类参数传给内核。由于它们不能同时运行,传递办法只有一个:Bootloader将参数放在某个约定的地方之后,再启动内核,内核启动后从这个地方获得参数。
除了约定好参数存放的地址外,还要规定参数的结构。Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。标记,就是一种数据结构;标记列表,就是挨着存放的多个标记。标记列表以标记ATAG_CORE 开始,以标记ATAG_NONE 结束。标记的数据结构为tag,它由一个tag_header结构和一个联合(union)组成。tag_header结构表示标记的类型及长度,比如是 表示内存还是表示命令行参数等。对于不同类型的标记使用不同的联合(union),比如表示内存时使用tag_mem32,表示命令行时使用 tag_cmdline。数据结构tag和tag_header定义在Linux内核源码的include/asm/setup.h头文件中:
1
2
3
4
5
6
7
8
|
struct
tag_header { u32 size; u32 tag; };
<br>
struct
tag {
struct
tag_header hdr;
union
{
struct
tag_corecore;
struct
tag_mem32mem;
struct
tag_videotextvideotext;
struct
tag_ramdiskramdisk;
struct
tag_initrdinitrd;
struct
tag_serialnrserialnr;
struct
tag_revisionrevision;
struct
tag_videolfbvideolfb;
struct
tag_cmdlinecmdline; <br>
/* * Acorn
specific */
struct
tag_acornacorn; <br>
/* * DC21285 specific
*/
struct
tag_memclkmemclk; } u; };
|
下面以设置内存标记、命令行标记为例说明参数的传递:
(1)设置标记 ATAG_CORE。
标记列表以标记 ATAG_CORE开始,假设Bootloader与内核约定的参数存放地址为0x30000100,则可以以如下代码设置标记 ATAG_CORE:
params = (struct tag *) 0x30000100; <br>params->hdr.tag = ATAG_CORE; params->hdr.size = tag_size (tag_core); <br>params->u.core.flags = 0; params->u.core.pagesize = 0; params->u.core.rootdev = 0; <br>params = tag_next (params);
其中,tag_next定义如下,它指向当前标记的末尾:
#define tag_next(t)((struct tag *)((u32 *)(t) + (t)->hdr.size))
(2)设置内存标记。
假设开发板使用的内存起始地址为0x30000000,大小为0x4000000,则内存标记可以如下设置:
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = 0x30000000;
params->u.mem.size = 0x4000000;
params = tag_next (params);
(3)设置命令行标记。
命令行就是一个字符串,它被用来控制内核的一些行为。比如"root=/dev /mtdblock2 init=/linuxrc console=ttySAC0"表示根文件系统在MTD2分区上,系统启动后执行的第一个程序为/linuxrc,控制台为ttySAC0(即第一个串 口)。
命令行可以在Bootloader中通过命令设置好,然后如下构造标记传给内核:
char *p = "root=/dev/mtdblock2 init=/linuxrc console=ttySAC0";
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
strcpy (params->u.cmdline.cmdline, p);
params = tag_next (params);
(4)设置标记ATAG_NONE。
标记列表以标记ATAG_NONE结束,如下设置:
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
常用Bootloader介绍
现在Bootloader种类繁多,比如x86上有LILO、GRUB等。对于ARM架构的CPU,有U-Boot、Vivi等。它们各有特点,下面列出Linux的开放源代码的Bootloader及其支持的体系架构,如表15.1所示。
开放源码的Linux引导程序
<table fck__showtableborders?="">
Bootloader Monitor 描述 X86 ARM PowerPC
LILO 否 Linux磁盘引导程序 是 否 否
GRUB 否 GNU的LILO替代程序 是 否 否
Loadlin 否 从DOS引导Linux 是 否 否
ROLO 否 从ROM引导Linux而不需要BIOS 是 否 否
Etherboot 否 通过以太网卡启动Linux系统的固件 是 否 否
LinuxBIOS 否 完全替代BUIS的Linux引导程序 是 否 否
BLOB 是 LART等硬件平台的引导程序 否 是 否
U-Boot 是 通用引导程序 是 是 是
RedBoot 是 基于eCos的引导程序 是 是 是
Vivi 是 Mizi公司针对SAMSUNG的ARM CPU设计的引导程序 否 是 否
对于本书使用的S3C2410/S3C2440开发板,U-Boot和Vivi是两个好选 择。Vivi是Mizi公司针对SAMSUNG的ARM架构CPU专门设计的,基本上可以直接使用,命令简单方便。不过其初始版本只支持串口下载,速度较 慢。在网上出现了各种改进版本:支持网络功能、USB功能、烧写YAFFS文件系统映像等。U-Boot则支持大多CPU,可以烧写EXT2、JFFS2 文件系统映像,支持串口下载、网络下载,并提供了大量的命令。相对于Vivi,它的使用更复杂,但是可以用来更方便地调试程序。
2 U-Boot分析与移植
2.1 U-Boot工程简介
U-Boot,全称为Universal Boot Loader,即通用Bootloader,是遵循GPL条款的开放源代码项目。其前身是由德国DENX软件工程中心的Wolfgang Denk基于8xxROM的源码创建的PPCBOOT工程。后来整理代码结构使得非常容易增加其他类型的开发板、其他架构的CPU(原来只支持 PowerPC);增加更多的功能,比如启动Linux、下载S-Record格式的文件、通过网络启动、通过PCMCIA/CompactFLash /ATA disk/SCSI等方式启动。增加ARM架构CPU及其他更多CPU的支持后,改名为U-Boot。
它的名字“通用”有两层含义:可以引导多种操作系统、支持多种架构的CPU。它支持如下操作 系统:Linux、NetBSD、 VxWorks、QNX、RTEMS、ARTOS、LynxOS等,支持如下架构的CPU:PowerPC、MIPS、x86、ARM、NIOS、 XScale等。
U-Boot有如下特性:
- 开放源码;
- 支持多种嵌入式操作系统内核,如Linux、NetBSD、VxWorks、QNX、RTEMS、ARTOS、LynxOS;
- 支持多个处理器系列,如PowerPC、ARM、x86、MIPS、XScale;
- 较高的可靠性和稳定性;
- 高度灵活的功能设置,适合U-Boot调试、操作系统不同引导要求、产品发布等;
- 丰富的设备驱动源码,如串口、以太网、SDRAM、FLASH、LCD、NVRAM、EEPROM、RTC、键盘等;
- 较为丰富的开发调试文档与强大的网络技术支持;
- 支持NFS挂载、RAMDISK(压缩或非压缩)形式的根文件系统
- 支持NFS挂载、从FLASH中引导压缩或非压缩系统内核;
- 可灵活设置、传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与产品发布,尤对Linux支持最为强劲;
- 支持目标板环境变量多种存储方式,如FLASH、NVRAM、EEPROM;
- CRC32校验,可校验FLASH中内核、RAMDISK镜像文件是否完好;
- 上电自检功能:SDRAM、FLASH大小自动检测;SDRAM故障检测;CPU型号;
- 特殊功能:XIP内核引导;
可以从http://sourceforge.net/projects/u-boot获得U-Boot的最新版本,如果使用过程中碰到问题或是发现Bug,可以通过邮件列表网站http://lists.sourceforge.net/lists/listinfo/u-boot-users/获得帮助。
最新的更新代码地址http://www.denx.de/wiki/U-Boot/WebHome
2.2 U-Boot源码结构
本书在u-boot-1.1.6的基础上进行分析和移植,从sourceforge网站下载u-boot-1.1.6.tar.bz2后解压即得到全部源码。U-Boot源码目录结构比较简单、独立,目录结构也比较浅,很容易全部掌握。
u-boot-1.1.6根目录下共有26个子目录,可以分为4类:
(1)平台相关的或开发板相关的。
(2)通用的函数。
(3)通用的设备驱动程序。
(4)U-Boot工具、示例程序、文档。
先将这26个目录的功能与作用如表15.2所示。
表2 U-Boot顶层目录说明
<table fck__showtableborders?="">
目录 特性 解释说明
board 开发板相关 对应不同配置的电路板(即使CPU相同),比如smdk2410、sbc2410x
cpu 平台相关 对应不同的CPU,比如arm920t、arm925t、i386等;在它们的子目录下仍可以进一步细分,比如arm920t下就有at91rm9200、s3c24x0
lib_i386类似 某一架构下通用的文件
include 通用的函数 头文件和开发板配置文件,开发板的配置文件都放在include/configs目录下,U-Boot没有make menuconfig类似的莱单来进行可视化配置,需要手动地修改配置文件中的宏定义
lib_generic 通用的库函数,比如printf等
common 通用的函数,多是对下一层驱动程序的进一步封装
disk 通用的设备驱动程序 硬盘接口程序
drivers 各类具体设备的驱动程序,基本上可以通用,它们通过宏从外面引入平台/开发板相关的函数
dtt 数字温度测量器或者传感器的驱动
fs 文件系统
nand_spl U-Boot一般从ROM、NOR Flash等设备启动,现在开始支持从NAND Flash启动,但是支持的CPU种类还不多
net 各种网络协议
post 上电自检程序
rtc 实时时钟的驱动
doc 文档 开发、使用文档
examples 示例程序 一些测试程序,可以使用U-Boot下载后运行
tools 工具 制作S-Record、U-Boot格式映像的工具,比如mkimage
U-Boot中各目录间也是有层次结构的,虽然这种分法不是绝对的,但是在移植过程中可以提供一些指导意义,如图2所示。
2 U-Boot顶层目录的层次结构
比如common/cmd_nand.c文件提供了操作NAND Flash的各种命令,这些命令通过调用drivers/nand/nand_base.c中的擦除、读写函数来实现。这些函数针对NAND Flash的共性作了一些封装,将平台/开发板相关的代码用宏或外部函数来代替。而这些宏与外部函数,如果与平台相关,就要在下一层次的cpu /xxx(xxx表示某型号的CPU)中实现;如果与开发板相关,就要在下一层次的board/xxx目录(xxx表示某款开发板)中实现。本书移植的 U-Boot,就是在cpu/arm920t/s3c24x0目录下增加了一个nand_flash.c文件来实现这些函数。
以增加烧写yaffs文件系统映像的功能为例──就是在common目录下的 cmd_nand.c中增加命令,比如nand write.yaffs:这个命令要调用drivers/nand/nand_util.c中的相应函数,针对yaffs文件系统的特点依次调用擦除、烧 写函数。而这些函数依赖于drivers/nand/nand_base.c、cpu/arm920t/s3c24x0/nand_flash.c文件中 的相关函数。
目前u-boot-1.1.6支持10种架构──根目录下有10个类似lib_i386的目 录、31个型号(类型)的CPU──cpu目录下有31个子目录,214种开发板──board目录下有214个子目录,很容易从中找到与自己的板子相似 的配置,在上面稍作修改即可使用。
2.3 U-Boot的配置、编译、连接过程
1. U-Boot初体验
u-boot-1.1.6中有几千个文件,要想了解对于某款开发板,使用哪些文件、哪个文件首先执行、可执行文件占用内存的情况,最好的方法就是阅读它的Makefile。
根据顶层Readme文件的说明,可以知道如果要使用开发板board/<board_name>,就先执行“make <board_name>_config”命令进行配置,然后执行“make all”,就可以生成如下3个文件:
- u-boot.bin:二进制可执行文件,它就是可以直接烧入ROM、NOR Flash的文件。
- u-boot:ELF格式的可执行文件
- u-boot.srec:Motorola S-Record格式的可执行文件
对于S3C2410的开发板,执行“make smdk2410_config”、“make all”后生成的u-boot.bin可以烧入NOR Flash中运行。启动后可以看到串口输出一些信息后进入控制界面,等待用户的输入。
对于S3C2440的开发板,烧入上面生成的u-boot.bin,串口无输出,需要修改代码。
在修改代码之前,先看看上面两个命令“make smdk2410_config”、“make all”做了什么事情,以了解程序的流程,知道要修改哪些文件。
另外,编译U-Boot成功后,还会在它的tools子目录下生成一些工具,比如mkimage等。将它们复制到/usr/local/bin目录下,以后就可以直接使用它们了,比如编译内核时,会使用mkimage来生成U-Boot格式的内核映像文件uImage。
2. U-Boot的配置过程
在顶层Makefile中可以看到如下代码:
SRCTREE:= $(CURDIR)
……
MKCONFIG:= $(SRCTREE)/mkconfig
……
smdk2410_config:unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
假定我们在u-boot-1.1.6的根目录下编译,则其中的MKCONFIG就是根目录下 的mkconfig文件。$(@:_config=)的结果就是将“smdk2410_config”中的“_config”去掉,结果为 “smdk2410”。所以“make smdk2410_config”实际上就是执行如下命令:
./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0
再来看看mkconfig的作用,在mkconfig文件开头第6行给出了它的用法:
06 # Parameters: Target Architecture CPU Board [VENDOR] [SOC]
这里解释一下概念,对于S3C2410、S3C2440,它们被称为SoC(System on Chip),上面除CPU外,还集成了包括UART、USB控制器、NAND Flash控制器等等设备(称为片内外设)。S3C2410/S3C2440中的CPU为arm920t。
以下,分步骤分析mkconfig的作用:
(1)确定开发板名称BOARD_NAME。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
11 APPEND=no# Default: Create
new
config file
12 BOARD_NAME=
""
# Name to print in make output
13
14
while
[ $# -gt 0 ] ;
do
15
case
"$1"
in
16 --) shift ;
break
;;
17 -a) shift ; APPEND=yes ;;
18 -n) shift ; BOARD_NAME=
"${1%%_config}"
; shift ;;
19 *)
break
;;
20 esac
21 done
22
23 [
"${BOARD_NAME}"
] || BOARD_NAME=
"$1"
|
对于“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,其中没有“--”、“-a”、“-n”等符号,所以第14~22行没做任何事情。第11、12行两个变量仍维持原来的值。
执行完第23行后,BOARD_NAME的值等于第1个参数,即“smdk2410”。
(2)创建到平台/开发板相关的头文件的链接。
略过mkconfig文件中的一些没有起作用的行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
30 #
31 # Create link to architecture specific headers
32 #
33
if
[
"$SRCTREE"
!=
"$OBJTREE"
] ; then
……
45
else
46 cd ./include
47 rm -f asm
48 ln -s asm-$2 asm
49 fi
50
|
第33行判断源代码目录和目标文件目录是否一样,可以选择在其他目录下编译U-Boot,这可以令源代码目录保持干净,可以同时使用不同的配置进行编译。不过本书直接在源代码目录下编译的,第33行的条件不满足,将执行else分支的代码。
第46~48行进入include目录,删除asm文件(这是上一次配置时建立的链接文件),然后再次建立asm文件,并令它链接向asm-$2目录,即asm-arm。
继续往下看代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
51 rm -f asm-$2/arch
52
53
if
[ -z
"$6"
-o
"$6"
=
"NULL"
] ; then
54 ln -s ${LNPREFIX}arch-$3 asm-$2/arch
55
else
56 ln -s ${LNPREFIX}arch-$6 asm-$2/arch
57 fi
58
59
if
[
"$2"
=
"arm"
] ; then
60 rm -f asm-$2/proc
61 ln -s ${LNPREFIX}proc-armv asm-$2/proc
62 fi
63
|
第51行删除asm-$2/arch目录,即asm-arm/arch。
对于“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,$6为“s3c24x0”,不为空,也不是“NULL”,所以第53行的条件不满足,将执行else分支。
第56行中,LNPREFIX为空,所以这个命令实际上就是:ln -s arch-$6 asm-$2/arch,即:ln -s arch-s3c24x0 asm-arm/arch。
第60、61行重新建立asm-arm/proc文件,并让它链接向proc-armv目录。
(3)创建顶层Makefile包含的文件include/config.mk。
对于“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,上面几行代码创建的config.mk文件内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
64 #
65 # Create include file
for
Make
66 #
67 echo
"ARCH = $2"
> config.mk
68 echo
"CPU = $3"
>> config.mk
69 echo
"BOARD = $4"
>> config.mk
70
71 [
"$5"
] && [
"$5"
!=
"NULL"
] && echo
"VENDOR = $5"
>> config.mk
72
73 [
"$6"
] && [
"$6"
!=
"NULL"
] && echo
"SOC = $6"
>> config.mk
74
|
ARCH = arm
CPU = arm920t
BOARD = smdk2410
SOC = s3c24x0
(4)创建开发板相关的头文件include/config.h。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
75 #
76 # Create board specific header file
77 #
78
if
[
"$APPEND"
=
"yes"
]# Append to existing config file
79 then
80 echo >> config.h
81
else
82 > config.h# Create
new
config file
83 fi
84 echo
"/* Automatically generated - do not edit */"
>>config.h
85 echo
"#include <configs/$1.h>"
>>config.h
86
|
前面说过,APPEND维持原值“no”,所以config.h被重新建立,它的内容如下:
/* Automatically generated - do not edit */
#include <configs/smdk2410.h>"
现在总结一下,配置命令“make smdk2410_config”,实际的作用就是执行“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令。假设执行“./mkconfig $1 $2 $3 $4 $5 $6”命令,则将产生如下结果:
(1)开发板名称BOARD_NAME等于$1;
(2)创建到平台/开发板相关的头文件的链接:
ln -s asm-$2 asm
ln -s arch-$6 asm-$2/arch
ln -s proc-armv asm-$2/proc# 如果$2不是arm的话,此行没有
(3) 创建顶层Makefile包含的文件include/config.mk。
ARCH = $2
CPU = $3
BOARD = $4
VENDOR = $5# $5为空,或者是NULL的话,此行没有
SOC = $6# $6为空,或者是NULL的话,此行没有
(4)创建开发板相关的头文件include/config.h。
/* Automatically generated - do not edit */
#include <configs/$1.h>"
从这4个结果可以知道,如果要在board目录下新建一个开发 板<board_name>的目录,则在include/config目录下也要建立一个文件<board_name>.h,里 面存放的就是开发板<board_name>的配置信息。
U-Boot还没有类似Linux一样的可视化配置界面(比如使用make menuconfig来配置),要手动修改配置文件include/config/<board_name>.h来裁减、设置U-Boot。
配置文件中有两类宏:
(1)一类是选项(Options),前缀为“CONFIG_”,它们用于选择CPU、SOC、开发板类型,设置系统时钟、选择设备驱动等。比如:
#define CONFIG_ARM920T1/* This is an ARM920T Core*/
#defineCONFIG_S3C24101/* in a SAMSUNG S3C2410 SoC */
#define CONFIG_SMDK24101/* on a SAMSUNG SMDK2410 Board */
#define CONFIG_SYS_CLK_FREQ12000000/* the SMDK2410 has 12MHz input clock */
#define CONFIG_DRIVER_CS89001/* we have a CS8900 on-board */
(2)另一类是参数(Setting),前缀为“CFG_”,它们用于设置malloc缓冲池的大小、U-Boot的提示符、U-Boot下载文件时的默认加载地址、Flash的起始地址等。比如:
#define CFG_MALLOC_LEN(CFG_ENV_SIZE + 128*1024)
#defineCFG_PROMPT"100ASK> "/* Monitor Command Prompt*/
#defineCFG_LOAD_ADDR0x33000000/* default load address*/
#define PHYS_FLASH_10x00000000 /* Flash Bank #1 */
从下面的编译、连接过程可知,U-Boot中几乎每个文件都被编译和连接,但是这些文件是否包含有效的代码,则由宏开关来设置。比如对于网卡驱动drivers/cs8900.c,它的格式为:
#include <common.h>/* 将包含配置文件include/config/<board_name>.h */
……
#ifdef CONFIG_DRIVER_CS8900
/* 实际的代码 */
……
#endif/* CONFIG_DRIVER_CS8900 */
如果定义了宏CONFIG_DRIVER_CS8900,则文件中包含有效的代码;否则,文件被注释为空。
可以这样粗糙地认为,“CONFIG_”除了设置一些参数外,主要用来设置U-Boot的功能、选择使用文件中的哪一部分;而“CFG_”用来设置更细节的参数。
3. U-Boot的编译、连接过程
配置完后,执行“make all”即可编译,从Makefile中可以了解U-Boot使用了哪些文件、哪个文件首先执行、可执行文件占用内存的情况。
先确定用到哪些文件,下面只摘取Makefile中与arm相关的部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
117 include $(OBJTREE)/include/config.mk
118 exportARCH CPU BOARD VENDOR SOC
119
……
127 ifeq ($(ARCH),arm)
128 CROSS_COMPILE = arm-linux-
129 endif
……
163 # load other configuration
164 include $(TOPDIR)/config.mk
165
|
第117、164行用于包含其他的config.mk文件,第117行所要包含文件的就是在 上面的配置过程中制作出来的include/config.mk文件,其中定义了ARCH、CPU、BOARD、SOC等4个变量的值为arm、 arm920t、smdk2410、s3c24x0。
第164行包含顶层目录的config.mk文件,它根据上面4个变量的值确定了编译器、编译选项等。其中对我们理解编译过程有帮助的是BOARDDIR、LDFLAGS的值,config.mk中: