2012年(135)
分类:
2012-09-19 11:50:32
原文地址:U-BOOT全线移植(1) 作者:iibull
【摘要】本节介绍了bootloader的基本概念。首先分析了为什么要针对特定的CPU和开发板移植bootloader的必要性。然后介绍了两 种如何在裸板中烧写bootloader的方法以及如何确定烧写地址。其次介绍了产品发布的启动加载模式和开发使用的下载模式(更新内核文件系统及 bootloader自身)。最后介绍了bootloader的两个通用启动阶段的流程及代码特性和运行位置。
【关键词】bootloader,烧写,复位地址,固化loader,启动加载,stage1,位置无关
一 bootloader介绍
1.1 Bootloader移植的必要性
Bootloader是与系统硬件环境高度相关的初始化软件,它担负着初始化硬件和引导操作系统
的双重责任。一些ARM平台可以共用同一种Bootloader,但是总的说来,每一个特定系统的Bootloader都会有所不同。
Bootloader广泛用于有操作系统的手持终端设备、智能家电及机顶盒等嵌入式设备上,它负责完成硬件初始化、操作系统引导和系统配制等。
Bootloader移植是在特定硬件平台上操作系统移植至关重要的一步。
1.2 BootLoader所支持的CPU和嵌入式系统板
每种不同的CPU体系结构都有不同的BootLoader。有些
BootLoader也支持多种体系结构的CPU,比如U-BOOT就同时支持ARM、MIPS、POWERPC等体系结构。除了依赖于CPU的体系结构
外,BootLoader实际上也依赖于具体的嵌入式板级设备的配置。也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种CPU而构建的,要想
让运行在一块板子上的BootLoader程序也能运行在另一块板子上,通常也都需要修改BootLoader的源程序。
1.3. Boot Loader的烧录和存储
系统加电或复位后,所有的CPU通常都从某个由CPU制造商预先安排的地址上取指令。比
如,at91rm9200的CPU在复位时通常都从地址0x00000000取它的第一条指令。而MPC8260高端启动则时从0xfff00000开始
取指。这个地址依据特定的CPU而定。通常片外启动时,基于CPU构建的嵌入式系统通常都有某种类型的固态存储设备(EEPROM或FLASH
等,at91rm9200是0x10000000)被映射到这个预先安排的地址上。因此在系统加电后,CPU将首先执行Boot Loader程序。
那么bootloader最初是怎么烧写到flash中的呢?对于一个裸板怎么让它跑起来呢?有两种方式:
² 通过片内固化的loader加载bootloader:通常某些CPU内部ROM中固化了一段程序可以用于最初的程序下载,如AT91RM9200。这时 下载的程序是在内部RAM中运行的,大小有一定限制,然后由这段程序继续交互下载真正要烧写到flash中的程序,将其保存在外部RAM中,最终烧写到 flash中。
² 通过JTAG或者仿真器下载:通常这些烧录工具可以直接操作flash,对其进行编程烧录。需要专门的工具。这种情况多用于那些没有固化loader的CPU,如三星系列。
上图是一个同时装有Boot Loader、内核的启动参数、内核映像和根文件系统映像的固态存储设备的典型空间分配结构图。上述顺序是可变的,但bootloader首地址一定要在CPU复位取指的地址上。
1.4 Boot Loader的操作模式(Operation Mode)
主机和目标机之间一般通过串口建立连接,BootLoader软件在执行时通常会通过串口来进行数据传输,如输出打印信息到串口,从串口读取用户控制字符。
大多数Boot Loader都包含两种不同的操作模式:启动加载模式和下载模式,这种区别仅对于开发人员才有意义。从最终用户的角度看,BootLoader的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。
² 启动加载(Boot loading)模式:这种模式也称为自主模式bootstrap。也即Boot Loader将存储在目标板Flash中的内核和文件系统的镜像装载到SDRAM中,整个过程无需用户的介入。这种模式是BootLoader的正常工作 模式,因此在嵌入式产品发布的时候,BootLoader显然必须工作在这种模式下。
² 下载Downloading模式:在这种模式下,目标机上的BootLoader将通过串口连接或网络连接等通信手段从宿主机Host下载文件,比如下载 内核映像和根文件系统映像等。从主机下载的文件通常首先被BootLoader保存到目标机的RAM中,然后再被BootLoader写到目标机上的 FLASH类固态存储设备中。BootLoader的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新(bootloader自 身也可以这样更新)也会使用Boot Loader的这种工作模式。工作于这种模式下的BootLoader通常都会向它的终端用户提供一些简单的命令行接口。
像U-BOOT等这样功能强大的BootLoader通常同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。比如,U-BOOT 在启动时处于正常的启动加载模式,但是它会延时几秒(在配置文件中可以设定)等待终端用户按下任意键而将其切换到下载模式(相当于bios下按del键可 进入系统配置界面一样,设置从光盘启动可进行重装),如果在给定时间内没有用户按键,则U-BOOT继续启动,进行正常的启动加载。
1.5 Bootloader与主机之间进行文件传输所用的通信设备及协议
最常见的情况就是,目标机上的BootLoader通过串口与主
机之间进行文件传输,传输协议通常是kermit / xmodem /
ymodem协议中的一种。但是,串口传输的速度是有限的,因此如果该Bootloader对目标板的网卡支持良好,还可以通过以太网连接并借助TFTP
(Trivial File Transfer
Protocol)协议来下载文件是个更好的选择。此时,主机方所用的软件也要考虑,比如,在通过以太网连接和TFTP协议来下载文件时,主机方必须有一
个软件用来提供TFTP服务,如Windows平台上的tftpd.exe等或Linux下面的tftp服务器。
1.6 Bootloader的通用执行流程
从操作系统的角度看,Bootloader的总目标就是正确地调用内核来执行。另外,由于Bootloader的实现依赖于 CPU的体系结构,因此大多数Bootloader都分为stagel和stage2两大部分。依赖于CPU体系结构的代码,比如设备初始化代码等,通常 都放在stagel中,而且通常都用汇编语言来实现,以达到短小精悍和高效的目的。而stage2则通常用C语言来实现,这样可以实现更复杂的功能,而且 代码会具有更好的可读性和可移植性。
Bootloader的stagel为位置无关代码,通常在FLASH中运行。所有的指令为相对寻址,可以在任何位置运行。通常包括以下步骤(以执行的先后顺序):
Ø 硬件设备初始化(配置SDRAM存储控制器及IO),中断初始化;
Ø 为加载Bootloader的stage2准备RAM空间(这个地址由链接脚本指定,为运行域地址,通常为RAM的高端地址),测试内存空间是否有效;
Ø 拷贝Bootloader的stage2到RAM空间中;
Ø 设置好堆栈;
Ø 跳转到stage2的C入口点。
Bootloader的stage2通常被拷贝到RAM中运行,这样可以提高运行速度。通常包括以下步骤(以执行的先后顺序):
Ø 初始化本阶段要使用到的硬件设备;
Ø 检测系统内存映射(memory map);
Ø 没有用户干预时将kernel映像和根文件系统映像从flash读到RAM空间中;
Ø 为内核设置启动参数;
Ø 调用内核。
U-boot基础
现在为Linux开放源代码Bootloader有很多,blob、 redboot及U-BOOT等,其中U-BOOT是目前用来开发嵌入式系统引导代码使用最为广泛的Bootloader。它支持POWERPC、ARM、MIPS和 X86等处理器,支持嵌入式操作系统有Linux、Vxworks及NetBSD等。
|-- board 平台依赖,存放电路板相关的目录文件
|-- common 通用多功能函数的实现
|-- cpu 平台依赖,存放cpu相关的目录文件
|-- disk 通用。硬盘接口程序
|-- doc 文档
|-- drivers 通用的设备驱动程序,如以太网接口驱动
|-- dtt
|-- examples 应用例子
|-- fs 通用存放文件系统的程序
|-- include 头文件和开发板配置文件,所有开发板配置文件放在其configs里
|-- lib_arm 平台依赖,存放arm架构通用文件
|-- lib_generic 通用的库函数
|-- lib_i386 平台依赖,存放x86架构通用文件
|-- lib_m68k 平台依赖
|-- lib_microblaze 平台依赖
|-- lib_mips 平台依赖
|-- lib_nios 平台依赖
|-- lib_ppc平台依赖,存放ppc架构通用文件
|-- net 存放网络的程序
|-- post 存放上电自检程序
|-- rtc rtc的驱动程序
`-- tools 工具
详细实例:
² board:开发板相关的源码,不同的板子对应一个子目录,内部放着主板相关代码。 Board/at91rm9200dk/at91rm9200.c, config.mk, Makefile, flash.c ,u-boot.lds等都和具体开发板的硬件和地址分配有关。
² common:与体系结构无关的代码文件,实现了u-boot所有命令,其中内置了一个shell脚本解释器(hush.c, a prototype Bourne shell grammar parser), busybox中也使用了它。
² cpu:与cpu相关代码文件,其中的所有子目录都是以u-boot所支持的cpu命名。
cpu/at91rm9200/at45.c, at91rm9200_ether.c, cpu.c, interrupts.c serial.c, start.S, config.mk, Makefile等。其中:
cpu.c负责初始化CPU、设置指令Cache和数据Cache等;
interrupt.c负责设置系统的各种中断和异常,比如快速中断、开关中断、时钟中断、软件中断、预取中止和未定义指令等;
start.S负责u-boot启动时执行的第一个文件,它主要是设置系统堆栈和工作方式,为跳转到C程序入口点做准备;
at91rm9200_ether.c和serial.c很重要,这是系统能够下载资源的前提。
² disk:设备分区处理代码。
² doc:u-boot相关文档。
² drivers:u-boot所支持的设备驱动代码, 网卡、支持CFI的Flash、串口和USB总线等。
² fs: u-boot所支持文件系统访问存取代码, 如jffs2。
² include:u-boot head文件,主要是与各种硬件平台相关的头文件,如include/asm-arm/arch-at91rm9200/AT91RM9200.h(硬件寄存器名称及地址的定义), hardware.h (内存及flash地址以及IO物理地址和虚拟地址的定义),include/asm-arm/proc-armv(与具体的CPU无关,无需移植)。
² net:与网络有关的代码,BOOTP协议、TFTP协议、RARP协议代码实现. 无需移植。
² lib_arm:与arm体系相关的代码。
² tools:编译后会生成mkimage工具,用来对生成的raw bin文件加入u-boot特定的image_header.
主要功能如下:
² 系统引导,支持NFS挂载、RAMDISK(压缩或非压缩)形式的根文件系统;
² 支持NFS挂载、从FLASH中引导压缩或非压缩系统内核;
² 基本辅助功能,强大的操作系统接口功能;可灵活设置、传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与产品发布,尤对Linux支持最为强劲;
² 支持目标板环境参数多种存储方式,如FLASH、NVRAM、EEPROM;
² CRC32校验,可校验FLASH中内核、RAMDISK镜像文件是否完好;
² 设备驱动,串口、SDRAM、FLASH、以太网、LCD、NVRAM、EEPROM、键盘、USB、PCMCIA、PCI、RTC等驱动支持;
² 上电自检功能 SDRAM、FLASH大小自动检测;SDRAM故障检测;CPU型号;
² 特殊功能,XIP内核引导。
² ?得到所有命令列表
² Help:help usb, 列出USB功能的使用说明
² ping:注意只能开发板PING别的机器(AT91RM9200不支持,需要进行配置)
² setenv: 设置环境变量
Ø setenv serverip 192.168.0.1
Ø setenv ipaddr 192.168.0.56
Ø setenv bootcmd ‘tftp 32000000 vmlinux; kgo 32000000’
² saveenv:保存环境变量。在设置好环境变量以后, 保存变量值
² tftp:tftp 32000000 vmlinux, 把server(IP=环境变量中设置的serverip)中/tftpboot/下的vmlinux通过TFTP读入到物理内存32000000处
² bootp- 通过网络用 BootP/TFTP 协议来启动映象
² tftpboot- 通过网络用 TFTP 协议、设置服务器和客户机的 IP 地址进行映象文件传送
² kgo: 起动没有压缩的linux内核,kgo 32000000(AT91RM9200不支持)
² bootm:起动UBOOT TOOLS制作的压缩LINUX内核, bootm 3200000
² protect: 对FLASH进行写保护或取消写保护,protect on 1:0-3(就是对第一块FLASH的0-3扇区进行保护),protect off 1:0-3取消写保护
² erase:删除FLASH的扇区, erase 1:0-2(就是对每一块FLASH的0-2扇区进行删除)
² cp: 在内存中复制内容, cp 32000000 0 40000(把内存中0x32000000开始的0x40000字节复制到0x0处)
² mw: 对RAM中的内容写操作, mw 32000000 ff 10000(把内存0x32000000开始的0x10000字节设为0xFF)
² md: 修改RAM中的内容, md 32000000(内存的起始地址)
² flinfo: 列出flash的信息
² loadb: 准备用KERMIT协议接收来自kermit或超级终端传送的文件。
² nfs: nfs 32000000 192.168.0.12:aa.txt,把192.168.0.12(LINUX 的NFS文件系统)中的NFS文件系统中的aa.txt 读入内存0x32000000处。
最常用的几个命令如下:
² go- 在地址 'addr' 处开始程序执行
² run- 运行一个环境变量所定义的命令
² bootm- 从内存中进行运行经过mkimage加工的程序映象
² loadb- 通过串口线(kermit mode) 来装载二进制文件
² printenv- 打印环境变量
² setenv- 设置环境变量
² saveenv保存环境变量到内存
² tftp-通过网络下载文件
² protect,erase,flash读写
下面是 U-BOOT 中的简单环境变量
² baudrate波特率
² bootdelay boot 延迟
² bootcmd Boot 命令
² bootargs Boot 参数,传递给内核
² bootfile 默认下载启动的内核映象
² ipaddr 客户机 IP 地址
² serverip 服务器地址
² loadaddr 装载地址
² ethaddr 网卡 MAC 地址
和大多数的Bootloader一样,U-BOOT的启动分为两个阶段两个部分,依赖于CPU体系结构的代码主要放在stage1,且用汇编来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且具有更好的可读性和可移植性。
下面分别分析一下这两个阶段的启动流程:
第一阶段:基本的硬件初始化,为第二阶段程序运行建立环境(cpu/ at91rm9200/start.s文件的代码部分):
××××××××××××××××××××××××××××
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start) // 程序的入口在/cpu/××××/start.s中定义
SECTIONS
{
. = 0x00000000; // 程序链接的地址
. = ALIGN(4);
.text :
{
cpu/at91rm9200/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
armboot_end_data = .; //代码段结束地址
. = ALIGN(4);
.bss : { *(.bss) }
armboot_end = .; //整个U-boot印象的结束地址
}
××××××××××××××××××××××××××××
在此需要定义程序入口,由于一个可执行的Image必须要有一个入口点,并且只能有一个全局入口,通常这个入口就在ROM (flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本u-boot.lds来完成,该阶段需要依次完成的工作一般包括:
² CPU自身的初始化,它包括:CPU运行模式的设置(管理模式)、设置异常的入口地址和异常处理函数、运行时钟频率的设置等工作。
² 初始化GPIO和内存控制器。
² 为拷贝Stage2准备RAM空间。
² 进行自拷贝,将U-BOOT的Stage2拷贝到RAM中。
² 设置好堆栈。
² 跳转到Stage2的入口,从而转到RAM中执行,该工作是调用指令ldr pc, start armboot来完成的。
××××××××××××××××××××××××××
从1.1.2开始,u-boot有初始化SDRAM并拷贝自己到SDRAM运行的代码,而之前的版本就没有这个功能(详细查看下代码??的确如此,因此对于以前的版本TEXT_BASE没有起作用?实际测试下??)。board/ at91rm9200dk中config.mk文件(TEXT_BASE = 0x21f00000,32M RAM的设置,为第二阶段程序在RAM中的运行地址)用于设置程序编译连接的起始地址,在程序中要特别注意与地址相关指令的使用。
Board/at91rm9200dk/config.mk
TEXT_BASE = 0x21f00000(u-boot将被载入SDRAM的高端部分)
注意,对于不同的系统RAM大小可能不一样,要根据实际情况调整。
在/config.mk中
ifdef BOARD
sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk # include board specific rules
endif
CPPFLAGS := $(DBGFLAGS) $(OPTFLAGS) $(RELFLAGS) \
-D__KERNEL__ -DTEXT_BASE=$(TEXT_BASE)
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
export TEXT_BASE PLATFORM_CPPFLAGS PLATFORM_RELFLAGS CPPFLAGS CFLAGS AFLAGS
对于U-Boot 1.1.2以前的版本,并没有自拷贝的部分,若flash中首地址存放的是非压缩的u-boot.bin的,则启动部分是一直运行在flash中的;但对于AT91RM9200来说,他通常有三个文件loader.bin, boot.bin, u-boot.bin,flash中首先运行的是boot.bin其将压缩的u-boot.bin.gz解压拷贝到TEXT_BASE处运行,此时已经在RAM中了,因此也无需实现自拷贝了。
对于U-Boot 1.1.2以后的版本,若像AT91RM9200有boot.bin这样的过渡程序,则将u-boot.bin.gz解压到RAM中,此时运行地址和链接地址相同,无需拷贝;若没有,则u-boot.bin将在flash中执行初始化部分,然后将自身拷贝到RAM中执行。
// 比较运行地址和链接地址,如果当前已经在RAM中运行了,则无需拷贝到RAM中
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
当程序在Flash中运行时,执行程序跳转时必须要使用相对跳转指令,而不能使用绝对地址的跳转(即直接对PC操作)。如果使用绝对地址,那么,程序的取指是相对于当前PC位置向前或者向后的32MB空间内,而不会跳入SDRAM中。
×××××××××××××××××××××××××××××
在上述操作运行完成后,就进入到/lib_arm/board.c 中的start_armboot()函数运行,并建立起了一个基本的环境,此时的物理内存空间的分布就变成了如图所示的情况。
转入boatloader stage2的系统内存布局
第二阶段:运行U-BOOT的主体部分
该阶段以程序跳转到lib_arm/board.c中的start_armboot函数为标志,该函数同时也是C语言的开始函数,是整个启动代码的主体函数,同时还是整个U-BOOT的主体函数,该函数主要完成以下工作:
² 调用一系列的初始化函数,初始化本阶段使用到的硬件,如:
×××××××××××××××××××
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
board_init, /* basic board dependent setup */
interrupt_init, /* set up exceptions */
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
dram_init, /* configure available RAM banks */
display_dram_config,
#if defined(CONFIG_VCMA9)
checkboard,
#endif
NULL,
};
×××××××××××××××××××××
env_init:设置环境变量,初始化环境;
init_baudrate:设置串口的波特率;
serial_init:设置串口的工作方式;
dram_init:设置SDRAM的起始地址和大小;
² 检查存储器分配和使用情况:获取flash的bank分区情况、是否擦除、是否上锁等信息为以后flash相关命令使用;初始化系统内存分配函数,供后面的代码使用malloc等函数,U-BOOT没有使用其他现成的库所有函数的实现均在文件中),如果系统有液晶等显示设备,一并在此分配显示内存。
² 打印内存,flash、环境变量设置等信息。
² 等待几秒时间,如果有键盘输入,则进入命令模式,接收用户输入的命令并解释执行(如启动操作系统,更新flash内容)。
² 如果在给定的时间内没有用户输入或者在执行命令操作时收到了用户要求启动内核的命令(boot),则将把操作系统内核和根文件系统映像文件从flash中拷贝到RAM中相应位置,并在设置内核启动参数后跳转到内核映像的首地址处执行。
×××××××××××××××××××××××××××××
U-BOOT调用 Linux 内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到 MEM_START+0x8000地址处。在跳转时,要满足下列条件:
a) CPU寄存器的设置:R0=0;R1=机器类型 ID,本系统的机器类型ID=193。R2=启动参数标记列表在RAM中的起始基地址;
b) CPU模式:必须禁止中断(IRQs和FIQs);CPU必须工作在SVC模式;
c) Cache和MMU的设置:MMU 必须关闭;指令Cache可以打开也可以关闭;数据Cache必须关闭。
系统采用下列代码来进入内核函数:
void (*theKernel)(int zero, int arch);
theKernel = (void (*)(int, int))ntohl(hdr->ih_ep);
theKernel(0, bd->bi_arch_number);其中,hdr是image_header_t类型的结构体,hdr->ih_ep为entry point,指向内核的第一条指令地址,即Linux操作系统下的/kernel/arch/arm/boot/compressed/head.S汇编程序。theKernel()函数调用应该不会返回,如果该调用返回,则说明出错。
×××××××××××××××××××××××××××××
U-BOOT在AT91RM9200上的移植
在这里我主要介绍通过片内引导和片外引导, 片内引导通常主要采用串口下载并引导u-boot,并将程序被烧写到 Flash上,然后就可以通过跳线的方式从片外引导执行已经烧写到片外Flash上的引导程序(bootloader)。
3.1.1 片内引导
1) 片内引导的基本原理
系统上电,检测BMS,选择系统的启动方式,如果BMS为高电平,则系统从片内ROM启动。AT91RM9200的内部ROM上电后被映射到了0x0和0x100000处,在这两个地址处都可以访问到ROM。由于9200的ROM中固化了一个BOOTLOAER程序。所以PC从0X0处开始执行这个BOOTLOAER(准确的说应该是一级BOOTLOADER)。这个BOOTLOADER依次完成以下步骤:
² PLL SETUP。设置PLLB产生48M时钟频率提供给USB DEVICE。同时DEBUG USART也被初始化为48M的时钟频率。
² 相应模式下的堆栈设置
² 检测主时钟源(Main oscillator)
² 中断控制器(AIC)的设置
² C 变量的初始化
² 跳到主函数
完成以上步骤后,我们可以认为BOOT过程结束,接下来的就是LOADER的过程,或者也可以认为是装载二级BOOTLOER。AT91RM9200按照DATAFLASH、EEPROM、连接在外部总线上的8位并行FLASH的顺序依次来找合法的BOOT程序。所谓合法的指的是在这些存储设备的开始地址处连续的存放的32个字节,也就是8条指令必须是跳转指令或者装载PC的指令,其实这样规定就是把这8条指令当作是异常向量表来处理。必须注意的是第6条指令要包含将要装载的映像的大小。关于如何计算和写这条指令可以参考用户手册。一旦合法的映像找到之后,则BOOT程序会把找到的映像搬到内部SRAM中去,所以映像的大小是非常有限的,不能超过16K的大小。当BOOT程序完成了把合法的映像搬到SRAM的任务以后,接下来就进行存储器的REMAP,经过REMAP之后,SRAM从映设前的0X200000地址处被映设到了0X0地址并且程序从0X0处开始执行。而ROM这时只能在0X100000这个地址处看到了。至此9200就算完成了一种形式的启动过程。
如果BOOT程序在以上所列的几种存储设备中未找到合法的映像,则自动初始化DEBUG USART口和USB DEVICE口以准备从外部载入映像,大多数情况都是如此。对DEBUG口的初始化包括设置参数115200 8 N 1以及运行XMODEM协议。对USB DEVICE进行初始化以及运行DFU协议。现在用户可以从外部(假定为PC平台)载入你的映像了。在PC平台下,以WIN2000为例,你可以用超级终端来完成这个功能,但是还是要注意你的映像的大小不能超过13K。一旦正确从外部装载了映像,接下来的过程就是和前面一样重映设然后执行映像了。
注意:通常所说的片内引导是指没有烧写合法的印象的情况下,但并不意外着烧些了合法印象的情况下不能采用片内引导的方式。通常第一次下载了启动印象后就会选择片外启动的方式了。并且烧些的印象通常第6条指令都不含将要装载的映像的大小,所以片内启动时一般不能运行这些印象。关于片内引导的详细过程可以参看at91rm9200的芯片说明书――引导程序一章。
Boot program Flow Diagram
Device Setup
|
Boot SPI DataFlash Boot --> Download from DataFlash --> run
|
TWI EEPROM Boot --> Download from EEPROM --> run
|
Parallel Boot --> Download from 8-bit Device -->
|
| Xmodem protocol
| |---DBGU Serial Download ---------------------> run
|____|
| DFU protocol
|-----USB download -----------------------> run
at91rm9200片内引导流程图
2) at91rm9200片内引导u-boot的实现过程
at91rm9200内部本身有128k的片内rom,其固化了一个bootloader和uploader,其他存储设备上没有合法的映象时,片内引导将启动uploader,uploader开启xmodem协议,等待用户上传程序,上传的程序将载入片内SRAM,重映射,然后pc跳转到片内SRAM执行上传的用户程序,即loader.bin。
注:片内SRAM只有16k,除去3-4k片内启动程序的占用的部分数据空间,因此下载的程序大小限制在12k内。
裸板只能用片内引导方式,载入一个12k以内的小程序loader.bin到内部SRAM运行,而这个小程序初始化SDRAM后,再把u-boot.bin下载到SDRAM的高端运行(u-boot大于12k,不能直接下载的原因就在于此),pc跳到SDRAM的u-boot位置运行u-boot,u-boot启动后再用u-boot自己的命令把boot.bin 及u-boot.gz下载到SDRAM的低端,再用flash烧写命令烧到flash去,以后就可以片外flash启动了。
3.1.2 片外引导
如果BMS为低电平,则AT91RM9200会从片外的FLASH启动,这时片外的FLASH的起始地址就是0X0了,要求已经在此地址烧些了启动映象了,接下来的过程和片内启动的过程是一样的,只不过这时就需要自己写启动代码了,至于怎么写,大致的内容和ROM的BOOT差不多,不同的硬件设计可能有不一样的地方,但基本的都是一样的。由于片外FLASH可以设计的大,所以这里编写的BOOTLOADER可以一步到位,也就是说不用像片内启动可能需要BOOT好几级了。
对于AT91RM9200,通常选择在flash的首地址处放的是boot.bin,由其将u-boot.bin.gz解压到高端RAM中,再运行真正的u-boot.bin,也就是实际的启动映象。
以上三个文件是at91rm9200启动所需要的三个bin,他们的实现代码并不难。
3.2.1 loader.bin
执行流程,这个文件主要在片内启动从串口下载U-boot.bin代码时会用到,一般固化在CPU的内部ROM中,用户无需改动。
loader/entry.S init cpu
b main ---> crt0.S
--> copydata --> clearbss --> b boot
main.c --> boot -->
/*Get internel rom service address*/
/* Init of ROM services structure */
pAT91 = AT91C_ROM_BOOT_ADDRESS;
/* Xmodem Initialization */
--> pAT91->OpenSBuffer
--> pAT91->OpenSvcXmodem
/* System Timer initialization */
---> AT91F_AIC_ConfigureIt
/* Enable ST interrupt */
AT91F_AIC_EnableIt
AT91F_DBGU_Printk("XMODEM: Download U-BOOT ");
Jump.S
// Jump to Uboot BaseAddr exec
Jump((unsigned int)AT91C_UBOOT_BASE_ADDRESS) 跳到下载的U-boot.bin执行
××××××××××××××××××××××××××××××××××
lader.bin主要有3个功能,初始化SDRAM,启动xmodem接收u-boot并写到SDRAM中,pc跳转到SDRAM运行。
xmodem的实现
只需要接收部分,发送部分用win下的”超级终端”等工具就可。先找来协议文档,熟悉协议,看看现有的xmodem协议源码。协议本身并不复杂,只是它的握手部分实现有点技巧。接收端要不停的发送字符“C”到串口,发送端收到“C”后发送数据SOH和第一个数据包。接收端检测到SOH后停止发送“C”并开始处理数据。官方的loader启动了一个时间服务,每隔1s发送一个“C”,在这个我使用了偷懒的算法。
while(Getchar()!=AT91C_XMODEM_SOH)
{
if (0xFFFF==++n )
{
SendChar(AT91C_XMODEM_CRCCHR);
n=0;
}
}
握手解决了,后面的处理都没什么问题。
写SDRAM
unsigned char *pSdram = (unsigned char *)AT91C_UBOOT_BASE_ADDRESS;
for ( n = 0; n<128 ; n ++ )
{
*pSdram++=data[n];
}
PC跳转
添加一个文件jump.S到工程
AREA reset, CODE, READONLY
EXPORT Jump
Jump
mov pc, r0
END
;---------------------------------------------------------------------------------
在main中使用下面的函数跳转
Jump((unsigned int)AT91C_UBOOT_BASE_ADDRESS);
loader的调试过程
xmodem部分可以传一个调试文件,传进去后全部send回串口,看返回的信息就可以判断是否正常工作。
写SDRAM,依然是写入后再读出来看看是否一致,在这里卡了很久,发现每隔2个地址就不能使用,后来发现是SDRAM没有初始化,重写后正常。
Jump测试,得传入一个可以运行的程序到内存才能判断,用先前编译好的u-boot-1.0.0试一试,出现u-boot的提示符了,也就是说jump没问题。
×××××××××××××××××××××××××××××××××××
3.2.2 boot.bin执行流程
该文件会在从片内启动时由U-boot.bin下载到板子上,以后还会被烧写到片外Flash中,以便在片外启动时用它来引导并解压u-boot.gz,并跳转到解压后的u-boot来执行。
boot/entry.S
b main --> crt0.S --> copydata --> clearbss --> b boot
T91F_DBGU_Printk(" ");
AT91F_DBGU_Printk("************************************** ");
AT91F_DBGU_Printk("** Welcome to at91rm9200 ** ");
AT91F_DBGU_Printk("************************************** ");
boot/misc.s /* unzip uboot.bin.gz */
----> decompress_image(SRC,DST,LEN) --> gunzip
//jump to ubootBaseAddr exec 这里跳转到解压后的u-boot地址处直接开始执行u-boot
asm("mov pc,%0" : : "r" (DST));
修改main.c中下面2项
#define SRC 0x10010000 (u-boot.gz将烧入flash的位置)
#define DST 0x21f00000 (u-boot.gz被解压后载入SDRAM的位置,和loader中保持一致)
3.2.1 uboot.bin执行流程
u-boot/cpu/at91rm9200/start.S
start --->reset
---> copyex ---> cpu_init_crit
---> /* set up the stack */ --> start_armboot
u-boot/lib_arm/board.c
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
board_init, /* basic board dependent setup */
interrupt_init, /* set up exceptions */
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
dram_init, /* configure available RAM banks */
display_dram_config,
checkboard,
NULL,
};
---> start_armboot ---> call init_sequence
---> flash_init --> display_flash_config
---> nand_init ---> AT91F_DataflashInit
---> dataflash_print_info --> env_relocate
---> drv_vfd_init --> devices_init --> jumptable_init
---> console_init_r --> misc_init_r --> enable_interrupts
---> cs8900_get_enetaddr --> board_post_init -->
u-boot/common/main.c
for (;;)
{ /* shell parser */
main_loop () --> u_boot_hush_start --> readline
--> abortboot
-->printf("Hit any key to stop autoboot: %2d ", bootdelay);
}
以上是at91rm9200启动并进入u-boot的执行流分析。后面u-boot还会将uImage解压到特定的位置并开始执行内核代码。
第一级地址译码由存储控制器执行,即由具有附加功能的高级系统总线(ASB) 执行。译码将32位地址总线决定的4G的地址空间分为16 个256M字节的区域。区域1~ 8 对应EBI,和外部片选NC0 ~NCS7相联系。区域0为内部存储器地址,第二级译码提供1M字节内部存储空间。区域15为外设地址,且提供对高级外设总线(APB) 的访问。其它区域未使用,使用它们进行访问时将向发出访问请求的主机发出异常中断。注意,地址的转换都是按照字节为单位的。
物理存储空间分布
3.3.1 内部存储器映射
内部ROM:AT91RM9200集成了一个128-K字节的内部ROM。任何时候,ROM均被映射到地址0x10 0000。若复位时BMS 为高,即片内启动时,则在复位后到重新映射命令执行前,ROM有两个地址,可访问地址0x0。重映射之后SRAM将变为0地址,内部ROM地址就只为0x10 0000。
ROM容量为128KB,即对应0x20000。所以范围为0x100000-0x120000。
内部RAM:AT91RM9200集成了高速,16-K 字节的内部SRAM。复位后到重新映射命令执行前,只可访问SRAM 中0x20 0000的地址空间。重新映射后, SRAM 在地址0x0有效。
RAM容量为16KB,即对应0x4000,所以范围为0x200000-0x204000。
USB 主机端口:AT91RM9200集成了一个USB主机端口开放主机控制器接口(OHCI)。ASB可直接访问该接口寄存器,且同标准内部存储器一样映射到地址0x30 0000。
内部存储器映射
3.3.2 外部存储器映射
嵌入式存储设备通常主要是外部RAM 和作为永久存储媒质的Flash。
现在所用的AT91RM9200开发板所用的SDRAM是K4S281632F,其容量为4banks×2Mbits×16,即128Mbits=16Mbytes。SDRAM共有两片K4S281632F,数据总线位宽16,两片组成32位位宽,所以SDRAM容量为32MB。
现在所用的Flash芯片为Intel的28F128J3A,容量为16M,地址映射从0x10000000到0x10FF FFFF。现在将Flash分为128个扇区,每个扇区为128KB=0x20000,每个扇区分为两个擦除块,为64KB=0x10000。
-------------------------------------------------------------------
Chip Select 0――Flash(0x1000 0000-0x10FF FFFF)
0x1000 0000(第0扇区)
boot.bin Flash
0x1001 0000(第0扇区)
u-boot.bin.gz Flash
0x1002 0000(第1扇区)
uImage Flash
.。。。。
0x1012 0000(第9扇区)
ramdisk Flash
.
0x107E 0000(第127扇区)
u-boot环境变量 Flash
-------------------------------------------------------------------
Chip Select 1――SDRAM(0x2000 0000-0x2200 0000)
0x2000 0000
SDRAM
.。。。。
0x2100 0000
uImage SDRAM
0x2110 0000
ramdisk SDRAM
------------------------------------------------------------------
0x0000 0000 ROM |
0x1000 0000 boot.bin FLASH |
0x1001 0000 uboot.gz FLASH |
0x1002 0000 ulmage FLASH |
0x1012 0000 ramdisk FLASH |
U-BOOT环境变量 8M_FLASH 63 扇区 FLASH 16M_FLASH 127扇区 |
0x2000 0000 SDRAM |
0x2100 0000 ulmage SDRAM |
0x2110 0000 ramdisk SDRAM |
各种文件的内存分布图
为了使u-boot-1.0.0支持新的开发板,一种简便的做法是在u-boot已经支持的开发板中参考选择一种较接近板的进行修改, 幸运的是在u-boot-1.0.0中已经有了at91rm9200的支持。 下面将详细阐述对其进行移植所需要关注的几个方面:
l 修改原因:
硬件平台不同部分:由于目标板对GPIO、串口等硬件的使用不同或选择的RAM、flash等芯片的不同,都需要对相应的控制寄存器和硬件设备进行不同的初始化。
向bootloader增加新的功能:比如,有时想在目标平台上增加USB或Ethernet下载功能等,同样需要在源码中加入相应的代码。
l 移植相关内容:
² 在include/configs/at91rm9200dk.h 它包括开发板的CPU、系统时钟、RAM、Flash系统及其它相关的配置信息。与具体的板子相关,是移植的最重要文件。
² 在include/asm-arm/AT91RM9200.h, 该文件描述了9200寄存器的结构及若干宏定义。具体内容要参考相关处理器手册。相同CPU的此文件相同,拷贝一份即可,无需修改。
² 在cpu/at91rm9200/目录下别为cpu.c、interrupts.c和serial.c等文件。
Ø cpu.c 无需改动,缓存,中断堆栈初始化,MMU映射等
Ø interrupts.c无需修改。各种中断的处理函数,U-boot运行无需中断,同时实现为重启;定时中断实现为查询方式,主要用于Xmodem协议传输文件。
Ø serial.c 无需修改,串口初始化,接收发送等。
Ø 上述文件都是与CPU相关的,与板子本身的配置无关。通常选择一个相同的CPU下的相关文件即可,无需修改。
² 在board/at91rm9200dk/目录下分别为flash.c、at91rm9200dk.c、config.mk、Makefile和u-boot.lds。
Ø flash.c : u-boot读、写和删除Flash设备的源代码文件。由于不同开发板中Flash存储器的种类各不相同(是移植的重点,可以从其他CPU目录下看是否有相同的flash),所以,修改flash.c时需参考相应的Flash芯片手册。
Ø at91rm9200dk.c
板级初始化,DRAM地址初始化。修改文件
/* arch number of AT91RM9200DK-Board */
gd->bd->bi_arch_number = 251;
/* adress of boot parameters */
gd->bd->bi_boot_params = PHYS_SDRAM + 0x100;
体系结构号,CPU相关,同CPU此值相同。
bi_boot_params内核启动参数的首地址,通常为PHYS_SDRAM + 0x100;即SDRAM的100处,很重要,Linux内核移植时,此值需要匹配。
Ø config.mk
TEXT_BASE = 0x21f00000(u-boot将被载入SDRAM的高端部分)
注意,对于不同的系统RAM大小可能不一样,要根据实际情况调整。
Ø Makefile 无需修改,除非改动了at91rm9200dk.c的名字,目标文件要改动。
OBJS := at91rm9200dk.o flash.o
Ø u-boot.lds 链接脚本, 设置u-boot中各个目标文件的连接地址。无需修改
² Makefile
在u-boot-1.0.0/Makefile中
at91rm9200dk_config : unconfig
./mkconfig $(@:_config=) arm at91rm9200 at91rm9200dk
其中ARM是CPU的种类, at91rm9200是ARM CPU对应的代码目录,at91rm9200dk是自已主板对应的目录。