基于U-Boot的嵌入式系统双启动设计与实现
扫描二维码
随时随地手机看文章
引 言
随着计算机技术、微电子技术、现代制造工艺和设计能力的不断进步和发展,硬件的集成度越来越高,使嵌入式系统在生产、生活中被广泛采用。但随之而来的是用户需求也越来越高,为了充分发挥嵌入式设备的性能,需要一个强大的操作系统,而在操作系统运行之前需要一段程序完成基本的初始化操作进而启动操作系统,Boot Loader 就是这段程序。此举实现了系统从硬件启动到操作系统启动的过渡,它固化在硬件中,被认为是嵌入式系统不可缺少的一部分,其作用类似于 PC上的 BIOS 和GRUB。但当前嵌入式设备上实现的Boot Loader 大多只支持一个操作系统的引导,大大限制了嵌入式设备的使用。
本文结合在 ARM 平台上广泛使用的 U-Boot,详细讨论了嵌入式 Boot Loader 的整体架构和运行流程。阐述了系统主要硬件的原理和裸机驱动程序设计,包含系统时钟设置, CPU 模式设置,内存初始化,NAND FLASH 配置和操作方法, WinCE 和嵌入式 Linux 在 ARM 平台下启动的实现原理和方法。最终实现了在 ARM 平台上的双启动 Boot Loader。
1 系统的硬件平台
本文使用的开发平台为友善之臂 Mini6410 开发板, 使用 Samsung 公司设计生产的 S3C6410 处理器, 使用 2 片K4X1G163PC 构成 256 M SDRAM,将 K9GAG08U0E 作为永久存储器。
1.1 CPU
S3C6410 基于ARM1176JZF-S 内核 [1-2], 包括分立的16kB指令和 16kB数据 Cache,16kB指令和数据 TCM,及1个完全的 MMU,用以处理虚拟存储管理。S3C6410内部有3 个 PLL,分别是 APLL,MPLL 和EPLL。APLL 用于 ARM 时钟操作,MPLL 用于主时钟操作,EPLL 用于特殊用途。时钟操作被分为三组 :APLL 产生ARM 时钟 ;MPLL 产生主系统时钟,用于操作 AXI,AHB 和 APB 总线 ;EPLL 产生的时钟主要用于外设 IPS,对 CPU 的设置主要采用 CPU 模式、中断和时钟设置,Boot Loader 和操作系统运行在 SVC 模式, 在U-Boot 中无需中断,时钟的选择根据应用环境而定。
1.2 内存
本系统选用两片 64 M×16 bit 的Mobile DDR 芯片,连接到S3C6410 SRAM 控制器,工作频率为 133 MHz,在处理器内部时钟为 533 MHz 时,能够接近最高使用效率。内存的初始化主要是对 SRAM 控制器的初始化,具体的初始化步骤在S3C6410 手册上有详细说明 [3]。具体流程如下:
(1) 使 SRAM控制器进入配置模式;
(2) 填写内存芯片的时序参数,并开始内存芯片的初始化序列 ;
(3) 配置完成后检查状态标志位。
1.3 NANDFLASH
NANDFLASH作为嵌入式系统中广泛使用的永久性存储器 [4],具有容量大、改写速度快等优点,适用于大量数据的存储,但是由于 NANDFLASH的设计原理,在操作时有自己独特的要求 :只能以页为单位读写,而且写时只能将 1写为 0。所以在写入前必须以块为单位擦除。
S3C6410内部包含一个 NANDFLASH控制器,用户只需配置好 NANDFLASH 的时序参数,NANDFLASH 控制器即可自动产生所需时序 [5]。主要配置的时序为 CLE/ALE拉高到 nWE拉低的等待时间,nWE为低的持续时间,nWE拉高后 CLE/ALE继续保持为高的时间。
1.4 存储器规划与分配
将 NAND FLASH 分为如下区域 :Boot Loader,Linux_ kernel,Linux_rootfs,WinCE 等。 通过外部跳线,NAND FLASH 启动后,S3C6410 将自动加载前 8 k 内容到 S3C6410 的内部 SRAM(Steppingstone)并开始从地址 0 处运行,最终加载并引导操作系统,如图 1(a)所示。
引导操作系统时需要用户在控制台中输入命令选择启动的操作系统,系统将加载制定 NAND FLASH 中的内容到内存指定位置,图 1(b)为物理内存使用规划,该配置内容将保存在开发板配置文件 $(board).h 中。
2 U-Boot分析
U-Boot(Universal Boot Loader)是遵循 GPL 协议的开放源码项目。具有系统引导、上电自检、CRC32 校验、设备驱动、支持 NFS 挂载、支持多种方式存储等功能。在嵌入式领域被广泛使用。
U-Boot 属于两阶段启动的Boot Loader,在第一阶段由汇编语言完成,与处理器直接相关,完成 CPU 模式切换,禁止看门狗,初始化内存控制器,设置堆栈,重定位 U-Boot 运行位置等操作,最后跳转到第二阶段代码运行。第二阶段代码主要由 C 语言完成,与目标板相关,主要完成所有设备的初始化工作,最后加载并启动操作系统,具体流程如图 2 所示。
U-Boot 目标文件通过 <$(board)/u-boot.lds> 链接脚本控制,该文件定义了连接到目标文件中各段的名称和位置,从该文件可以看出,U-Boot 由 <$(CPU)/start.o> 文件开始, start.o由start.S 编译生成,包含了U-Boot 第一阶段的主要代码。第二阶段代码以<$(arch)/board.c> 为入口。
U-Boot 支持多种平台,其驱动程序代码位于 drivers 文件夹下,各设备的板级配置信息由 <include/configs/$(board).h> 文件控制,是移植过程中需要修改的主要文件。
为提高软件的复用性,系统将操作系统的引导函数作为独立的U-Boot 命令添加到 U-Boot 中,支持多种命令,其命令的实现代码保存在 Common 中,以“cmd_”作为文件前缀, 添加 U_Boot 命令时需使用 U-Boot 提供的 U_BOOT_CMD 宏声明。
U_BOOT_CMD 各参数的意义 :
(1) Name:命令的名称,用于唯一区别命令,在 U-Boot中如果命令的前缀不同,在终端可以直接输入前缀执行命令。
(2) Maxages:命令可以接收的最多的参数个数。
(3) Cmd:命令的实现函数,命令被执行时,该函数被调用。
(4) Uasege:短的帮助信息,使用 help时将打印该信息。
(5) Help:长的帮助信息,使用 helpCmd时打印该信息。在添加完代码后,还需在 <Common/Makefile> 文件中添加相应命令的目标文件。
3 U-Boot双系统驱动启动的实现
U-Boot 要启动操作系统,必须首先完成必要硬件的初始化,设置好操作系统的运行环境后加载并启动操作系统。
3.1 硬件初始化代码修改和配置
Mini6410 使用三星公司设计生产的基于 ARM1176JZF-S 内核的S3C6410 处理器 [6]。为提高代码复用度,减小移植难度,选用三星公司修改的 U-Boot1.1.6 版本,该版本支持与本开发板类似的 SMDK6410,适当修改后可以应用在本开发板上,可实现项目的主要功能。
修改 /Makefile :添加 mini6410 的编译命令 :
-CROSS_COMPILE= /usr/local/arm/4.2.2-eabi/usr/bin/arm- linux-
+ CROSS_COMPILE = arm-linux-
+ mini6410_nand_config :unconfig
+@$(MKCONFIG)mini6410 arm s3c64xx mini6410 samsung s3c6410 NAND ram256
复制参考板代码 :
board/samsung/smdk6410-> board/samsung/mini6410 include/configs/smdk6410.h-> include/config/mini6410.h
修改平台相关代码 :
Include/configs/Mini6410.h
Mini6410.h 包含了所有 Mini6410 开发板的配置选项,如SDRAM 的大小、位置,NAND FLASH 的大小,串口的配置, 网卡的型号,MAC 地址,IP 地址等信息,以及默认的内核启动参数等。
(2) CPU/s3c64xx/Start.s
Start.s 是整个U-Boot 的入口,包含了最基本的设置 CPU 模式的代码,可调用内存初始化函数、系统时钟初始化函数、第二阶段入口函数。本文件主要修改的内容是去掉 SMDK6410 中包含的 ONENAND 初始化代码。
(3) Board/Samsung/Mini6410/Mini6410.c
Mini6410.c 包含了一些板级初始化代码,包含网卡初始化函数的调用、LCD 初始化函数调用代码等,以及虚拟内存地址到物理内存地址的转换函数。本文件主要修改的内容是去掉 SMDK6410 中包含的 CS8900 网卡的初始化函数,添加了DM9000 的初始化函数和 USB 下载功能的支持函数。
(4) Common/Main.c
Main.c 包含了 U-Boot 接收并执行命令的主循环函数man_loop(),本文件的代码与体系无关,不用做其他修改。但为了提高 U-Boot 的可用性添加了菜单函数。
3.2 引导WinCE系统
Boot Loader 引导 WinCE 需要完成以下操作 :
设置 CPU为SVC模式。
完成 CPU,内存控制器,系统时钟,串口,Caches,TLBs的初始化。
解压 WinCE内核镜像文件头,检查校验和,加载内核到指定位置。
跳转之前禁用中断和 MMU。
解压文件头需要分析 WinCE 文件头格式[7],WinCE 镜像存在两种格式,分别为 nb0 和 bin。nb0 是原始的二进制镜像,可以直接烧到 FLASH/ROM 中,它不包括头,可以直接跳转到其入口执行,一般情况下采用 nb0 将内核下载到设备的RAM 中运行。bin 是一种二进制镜像格式,以片断为单位组织数据,每个片断都包括一个头,头中指定有起始地址、长度、校验值。Platform Builder 将 WinCE 内核所有文件以 bin 格式合并成一个文件,默认文件名为 nk.bin。Boot Loader 需要将nk.bin 分解成多个文件放到 RAM 中。
启动 WinCE 操作系统只需要完成必要的硬件初始化,设置好 WinCE 系统的运行环境,然后读取并校验 WinCE 镜像文件,由镜像文件头部获取 WinCE 的加载地址,并将 WinCE 保存到指定的位置后,跳转到其开始地址,就可以成功引导WinCE 操作系统,如图 3 所示。
3.3 引导 Linux系统
Boot Loader 引导 Linux 内核需要完成以下操作 :
(1) 设置 CPU为SVC模式。
(2) 完成 CPU,内存控制器,系统时钟,串口,Caches,TLBs的初始化。
(3) 加载 Linux内核镜像文件到内存指定位置。
(4) 设置Linux内核启动参数并跳转到内核。
图 3 启动 WinCE
通过Boot Loader 启动内核要传递三个参数 :将第一个参数放在寄存器 0 中,一般 r0=0;第二个参数放在寄存器 1 中,是机器类型 ID ;第三个参数放在寄存器 2 中,是启动参数标记列表(TaggedList)在 RAM 中的起始基地址。
其中机器ID 定义在<linux/arc/arm/tool/mach_types> 文件, 内核用机器 ID 检查是否支持当前平台;TaggedList 是Boot Loader 传递给内核的主要参数,包含系统的根设备标志,页面大小,内存的起始地址和大小,RAMDISK 的起始地址和大小,压缩的 RAMDISK 根文件系统的起始地址和大小,内核命令参数等。内核参数链表的格式和说明可以从内核源代码目录树的 <include/asm-arm/setup.h> 中找到, 参数链表必须以 ATAG_CORE 开始,以 ATAG_NONE 结束。这里的 ATAG_CORE,ATAG_NONE 是各参数的标记,是一个32 位值,例如:ATAG_CORE=0x54410001。其他参数标记还包括 ATAG_MEM32,ATAG_INITRD,ATAG_RAMDISK,ATAG_COMDLINE 等。每个参数标记就代表一个参数结构体,由各参数结构体构成参数列表。内核在启动时将解析该参数表,用于内核启动的配置。
Linux 内核的引导只需要完成必要的硬件初始化操作, 设置运行环境后,读取 Linux 内核到预先规划好的位置 ;设置好TaggedList 后,将TaggedList,MachID,内存参数,文件系统位置等信息传递给Linux 内核后就能完成 Linux 内核的引导,如图 4 所示。
图 4 启动 Linux
4 结 语
本文以三星 S3C6410 为处理器的 Mini6410 开发板为硬件平台,以 U-Boot 为基础,实现了在一个嵌入式设备上使用一个 Boot Loader 引导多个操作系统的目的。本文将系统启动的代码实现为一个独立的函数,有利于降低项目代码的耦合性,方便后续嵌入式系统的开发。