第10课 bootloader介绍(含
VIVI)
VIVI
课程内容介绍
PART I: Boot loader概述
PART II: VIVI
PART III:VIVI基本命令介绍
PART IV:本章小结
PART I
Bootloader概述
Bootloader简介
引导加载程序(Bootloader)是系统加电后运行的第一
段软件代码。回忆一下 PC 的体系结构我们可以知道,
PC 机中的引导加载程序由 BIOS(其本质就是一段固件
程序)和位于硬盘MBR(master boot record)中的OS
BootLoader(比如,LILO 和 GRUB 等)一起组成。
BIOS 在完成硬件检测和资源分配后,将硬盘MBR中的
BootLoader 读到系统的RAM 中,然后将控制权交给
OS BootLoader。BootLoader的主要运行任务就是将内
核映像从硬盘上读到RAM 中,然后跳转到内核的入口
点去运行,也即开始启动操作系统。(用图来演示PC
的启动过程)
Bootloader简介
而在嵌入式系统中,通常并没有像BIOS 那样
的固件程序(注:有的嵌入式 CPU 也会内嵌
一段短小的启动程序),因此整个系统的加载
启动任务就完全由BootLoader来完成。比如在
一个基于ARM core的嵌入式系统中,系统在上
电或复位时通常都从地址0x00000000 处开始
执行,而在这个地址处安排的通常就是系统的
BootLoader程序。(演示通信系统的一般系统
启动过程)
本节将从BootLoader的概念和启动过程来讨论
嵌入式系统的BootLoader。
Bootloader简介
BootLoader的概念
简单地说,BootLoader就是在操作系统内核运行之前
运行的一段小程序。通过这段小程序,我们可以初始
化硬件设备、建立内存空间的映射图,从而将系统的
软硬件环境带到一个合适的状态,以便为最终调用操
作系统内核准备好正确的环境。
通常,BootLoader是严重地依赖于硬件而实现的,特
别是在嵌入式世界。因此,在嵌入式世界里建立一个
通用的BootLoader几乎是不可能的。尽管如此,我们
仍然可以对BootLoader归纳出一些通用的概念来,以
指导用户特定的 BootLoader设计与实现。
Bootloader简介
1、BootLoader所支持的CPU和嵌入式单板
每种不同的CPU体系结构都有不同的
BootLoader。有些BootLoader也支持多种体系
结构的CPU,比如U-Boot就同时支持ARM、
PPC和MIPS 等一系列体系结构。除了依赖于
CPU的体系结构外,BootLoader实际上也依赖
于具体的嵌入式板级设备的配置。也就是说,
对于两块不同的嵌入式板而言,即使它们是基
于同一种CPU构建的,要想让运行在一块板子
上的BootLoader程序也能运行在另一块板子上,
通常也都需要修改BootLoader的源程序。
Bootloader简介
2、BootLoader的安装
系统加电或复位后,所有的CPU通常都从CPU
制造商预先安排的地址上取指令。比如,ARM
系列CPU在复位后都从地址0x00000000取它的
第一条指令。而嵌入式系统通常都有某种类型
的固态存储设备(比如:ROM、EEPROM或
FLASH等)被安排在这个起始地址上,因此在
系统加电后,CPU将首先执行BootLoader程序。
Bootloader简介
一个同时装有 BootLoader、内核的启动参数、
内核映像和根文件系统映像的固态存储设备的
典型空间分配结构图(这个图是Linux的典型配
置,不同的系统配置可以不同):
Bootloader简介
3、用来控制BootLoader的设备或机制
串口通讯是最简单也是最廉价的一种双机通讯
设备,所以往往在BootLoader中主机和目标机
之间都通过串口建立连接,BootLoader程序在
执行时通常会通过串口来进行I/O,比如:输
出打印信息到串口,从串口读取用户控制字符
等。当然如果认为串口通讯速度不够,也可以
采用网络或者USB通讯,那么相应的在
BootLoader中就需要编写各自的驱动。
Bootloader简介
4、BootLoader的启动过程
BootLoader的启动过程分为单阶段(Single
Stage)和多阶段(Multi-Stage)两种。通常
多阶段的BootLoader能提供更为复杂的功能,
以及更好的可移植性。从固态存储设备上启动
的BootLoader大多都是2阶段的启动过程,也
即启动过程可以分为 stage 1和 stage 2 两部
分。至于在 stage 1 和 stage 2中具体完成哪
些任务,将在下一小节中具体讨论。
Bootloader简介
5、BootLoader的操作模式
大多数 BootLoader都包含两种不同的操
作模式:“启动加载”模式和“下载”
模式。这种区别仅对于开发人员才有意
义。但从最终用户的角度看,
BootLoader的作用就是用来加载操作系
统的,而并不存在所谓的启动加载模式
与下载工作模式的区别。
Bootloader简介
启动加载(Boot loading)模式:这种模式也称为“自主
”(Autonomous)模式。即BootLoader从目标机上的某个固态存
储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户
的介入。这种模式是 BootLoader的正常工作模式,因此在嵌入式
产品发布的时侯,BootLoader显然必须工作在这种模式下。
下载(Downloading)模式:在这种模式下,目标机上的
BootLoader将通过串口连接或网络连接等通信手段从主机
(Host)下载文件,比如:下载内核映像和根文件系统映像等。
从主机下载的文件通常首先被 BootLoader保存到目标机的 RAM
中,然后再被BootLoader写到目标机上的FLASH 类固态存储设备
中。BootLoader的这种模式通常在第一次安装内核与根文件系统
时被使用;此外,以后的系统更新也会使用BootLoader的这种工
作模式。工作于这种模式下的BootLoader通常都会向它的终端用
户提供一个简单的命令行接口。
Bootloader简介
6、BootLoader与主机之间进行文件传输所用的通信设备
及协议
最常见的情况就是,目标机上的 BootLoader通过串口
与主机之间进行文件传输,传输协议通常是 xmodem
/ymodem/zmodem/kermit 协议中的一种。但是,
串口传输的速度是有限的,因此通过以太网连接并借
助 TFTP 协议来下载文件是个更好的选择。(问题:
TFTP协议是在第几层?它是基于那一个协议的?)
此外,在论及这个话题时,主机方所用的软件也要考
虑。比如,在通过以太网连接和 TFTP 协议来下载文
件时,主机方必须有一个软件用来提供 TFTP 服务
(tftpserver)。
Bootloader简介
BootLoader 的启动过程
BootLoader 的 stage1 通常包括以下步骤(以执
行的先后顺序):
硬件设备初始化。
为加载 BootLoader 的 stage2 准备 RAM 空间。
拷贝 BootLoader 的 stage2 到 RAM 空间中。
设置好堆栈。
跳转到 stage2 的 C 入口点。
Bootloader简介
BootLoader 的 stage2 通常包括以下步骤(以执
行的先后顺序):
初始化本阶段要使用到的硬件设备(比如网络
和串口)。
检测系统内存映射(memory map),得知内存
的大小等信息。
将 kernel 映像和根文件系统映像从 flash 上读
到 RAM 空间中。
为内核设置启动参数。(这个后面会讲)
调用内核。
Bootloader简介
1、BootLoader 的 stage1(通常用汇编来写)
a)基本的硬件初始化
这是 BootLoader 一开始就执行的操作,其目的是为
stage2 的执行以及随后的 kernel 的执行准备好一些基
本的硬件环境。它通常包括以下步骤(以执行的先后
顺序):
屏蔽所有的中断。为中断提供服务通常是 OS 设备驱
动程序的责任,因此在 BootLoader 的执行全过程中可
以不必响应任何中断。中断屏蔽可以通过写 CPU 的中
断屏蔽寄存器或状态寄存器(比如 ARM 的 CPSR 寄存
器)来完成。
Bootloader简介
设置 CPU 的速度和时钟频率。
RAM 初始化。包括正确地设置系统的内存控制
器的功能寄存器以及各内存库控制寄存器等。
初始化 LED。典型地,通过 GPIO 来驱动 LED
,其目的是表明系统的状态是 OK 还是 Error。
如果板子上没有 LED,那么也可以通过初始化
UART 向串口打印 BootLoader 的 Logo 字符信
息来完成这一点。
关闭 CPU 内部指令/数据 cache。
Bootloader简介
b)为加载 stage2 准备 RAM 空间
为了获得更快的执行速度,通常把 stage2 加
载到 RAM 空间中来执行,因此必须为加载
BootLoader 的 stage2 准备好一段可用的 RAM
空间范围。
由于 stage2 通常是 C 语言执行代码,因此在
考虑空间大小时,除了 stage2 可执行映像的
大小外,还必须把堆栈空间也考虑进来。此外,
空间大小最好是 memory page 大小(通常是
4KB)的倍数。一般而言,1M 的 RAM 空间已经
足够了。
Bootloader简介
另外,还必须确保所安排的地址范围的的确确
是可读写的 RAM 空间,因此,必须对你所安
排的地址范围进行测试。具体的测试方法可以
采用类似于 blob 的方法,也即:以 memory
page 为被测试单位,测试每个 memory page
开始的两个字是否是可读写的。为了后面叙述
的方便,我们记这个检测算法为:
test_mempage,其具体步骤如下:
先保存 memory page 一开始两个字的内容。
向这两个字中写入任意的数字。比如:向第一
个字写入 0x55,第 2 个字写入 0xaa。
Bootloader简介
然后,立即将这两个字的内容读回。显然,我们读到
的内容应该分别是 0x55 和 0xaa。如果不是,则说明
这个 memory page 所占据的地址范围不是一段有效的
RAM 空间。
再向这两个字中写入任意的数字。比如:向第一个字
写入 0xaa,第 2 个字中写入 0x55。
然后,立即将这两个字的内容立即读回。显然,我们
读到的内容应该分别是 0xaa 和 0x55。如果不是,则
说明这个 memory page 所占据的地址范围不是一段有
效的 RAM 空间。
恢复这两个字的原始内容。测试完毕。
为了得到一段干净的 RAM 空间范围,我们也可以将所
安排的 RAM 空间范围进行清零操作。
Bootloader简介
c)拷贝stage2到RAM中
拷贝时要确定两点:(1) stage2 的可执行映像在固态
存储设备的存放起始地址和终止地址;(2) RAM 空间
的起始地址。
d)设置堆栈指针sp
堆栈指针的设置是为了执行 C 语言代码作好准备。通
常我们可以把 sp 的值设置为(stage2_end-4),也即在
b)中所安排的那个 1MB 的 RAM 空间的最顶端(最大
处)(堆栈向下生长)。
此外,在设置堆栈指针 sp 之前,也可以关闭 led 灯,
以提示用户我们准备跳转到 stage2。
Bootloader简介
e)跳转到stage2的C入口点
在上述一切都就绪后,就可以跳转到
BootLoader 的 stage2 去执行了。比如,
在 ARM 系统中,这可以通过修改 PC 寄
存器为合适的地址来实现。
Bootloader简介
执行完Stage_1以后的内存情况:
Bootloader简介
2、BootLoader 的 stage2
正如前面所说,stage2 的代码通常用 C 语言来实现,
以便于实现更复杂的功能和取得更好的代码可读性和
可移植性。但是与普通 C 语言应用程序不同的是,在
编译和链接 BootLoader 这样的程序时,我们不能使用
glibc 库中的任何支持函数。其原因是显而易见的。
这就给我们带来一个问题,那就是从那里跳转进
main() 函数呢?(问题:为什么无法跳进main函数?)
直接把 main() 函数的起始地址作为整个 stage2 执行
映像的入口点或许是最直接的想法。但是这样做有两
个缺点:
1)无法通过main() 函数传递函数参数;
2)无法处理 main() 函数返回的情况。
Bootloader简介
解决方法:
.text
.globl _trampoline
_trampoline:
bl main
/* if main ever returns we just call it again */
b _trampoline
还可以通过给寄存器赋值来传递
参数,具体要看编译器的规定,
一般会使用r0,r1…来传递参数
Bootloader简介
a)初始化本阶段要使用到的硬件设备
这通常包括:(1)初始化至少一个串口,以
便和终端用户进行 I/O 输出信息;(2)初始
化计时器等。
在初始化这些设备之前,也可以重新把 LED 灯
点亮,以表明我们已经进入 main() 函数执行。
设备初始化完成后,可以输出一些打印信息,
程序名字字符串、版本号等。
Bootloader简介
b)检测系统的内存映射(memory map)
所谓内存映射就是指在整个 4GB 物理地址空
间中有哪些地址范围被分配用来寻址系统的
RAM 单元。比如,在 SA-1100 CPU 中,从
0xC000,0000 开始的 512M 地址空间被用作系
统的 RAM 地址空间,而在 Samsung
S3C44B0X CPU 中,从 0x0c00,0000 到
0x1000,0000 之间的 64M 地址空间被用作系
统的 RAM 地址空间。
Bootloader简介
虽然 CPU 通常预留出一大段足够的地址空间给系统
RAM,但是在搭建具体的嵌入式系统时却不一定会实
现 CPU 预留的全部 RAM 地址空间。也就是说,具体
的嵌入式系统往往只把 CPU 预留的全部 RAM 地址空
间中的一部分映射到 RAM 单元上,而让剩下的那部分
预留 RAM 地址空间处于未使用状态。由于上述这个事
实,因此 BootLoader 的 stage2 必须在它想干点什么
(比如,将存储在 flash 上的内核映像读到 RAM 空间中
) 之前检测整个系统的内存映射情况,也即它必须知道
CPU 预留的全部 RAM 地址空间中的哪些被真正映射到
RAM 地址单元,哪些是处于“unused”状态的。
Bootloader简介
c)加载内核映像和根文件系统映像
规划内存占用的布局
这里包括两个方面:(1)内核映像所占
用的内存范围;(2)根文件系统所占用
的内存范围。在规划内存占用的布局时,
主要考虑基地址和映像的大小两个方面。
Bootloader简介
对于内核映像,一般将其拷贝到从(MEM_START+
0x8000) 这个基地址开始的大约1MB大小的内存范围
内(嵌入式 Linux 的内核一般都不操过 1MB)。为什么
要把从 MEM_START 到 MEM_START+0x8000 这段
32KB 大小的内存空出来呢?这是因为 Linux 内核要在
这段内存中放置一些全局数据结构,如:启动参数和
内核页表等信息。
而对于根文件系统映像,则一般将其拷贝到
MEM_START+0x0010,0000 开始的地方。如果用
Ramdisk 作为根文件系统映像,则其解压后的大小一
般是1MB。
Bootloader简介
从Flash上拷贝
由于像 ARM 这样的嵌入式 CPU 通常都是在统
一的内存地址空间中寻址 Flash 等固态存储设
备的,因此从 Flash 上读取数据与从 RAM 单
元中读取数据并没有什么不同。用一个简单的
循环就可以完成从 Flash 设备上拷贝映像的工
作:
while(count) {*dest++ = *src++; /* they are
all aligned with word boundary */count -= 4;
/* byte number */};
Bootloader简介
d)设置内核的启动参数
应该说,在将内核映像和根文件系统映
像拷贝到 RAM 空间中后,就可以准备启
动 Linux 内核了。但是在调用内核之前,
应该作一步准备工作,即:设置 Linux
内核的启动参数(比如文件系统放在哪
里,内存多大等)。
Bootloader简介
e)调用内核
BootLoader 调用 Linux 内核的方法是直接跳转到内核的第一条指
令处,也即直接跳转到 MEM_START+0x8000 地址处。在跳转时,
下列条件要满足:
CPU 寄存器的设置:
R0=0;R1=机器类型 ID;R2=启动参数标记列表在 RAM 中起
始基地址。
CPU 模式:
必须禁止中断(IRQs和FIQs);CPU 必须处于 SVC 模式。
Cache 和 MMU 的设置:
MMU 必须关闭;指令 Cache 可以打开也可以关闭;数据 Cache
必须关闭。
PART II
VIVI
vivi概述
vivi是韩国Mizi公司开发的Bootloader,适用于
ARM9处理器。vivi也有上一节提到的两种工作
模式。启动加载模式可以在一定时间后(时间
长短可以修改)自行启动Linux内核,这是vivi
的默认模式。而在下载模式下,vivi为用户提
供了一个命令行接口,通过该接口可以使用
vivi的一些主要命令:load, part, param, boot,
flash.
vivi的配置和编译
下载vivi压缩包后,使用如下命令解压缩:
$ tar jxvf 进入vivi目录
我们使用如下命令配置vivi:
演示:
$ make menuconfig
我们可以自行配置,也可以使用默认的配置文
件进行自动配置。这些默认的配置文件放在
vivi/arch/def-configs目录下。
A)选择“Load on Alternate Configuration File”菜单,
在其中填上“arch/def-configs/smdk2410” or
“arch/def-configs/s3c2410-tk”来选择对应的单板配置
文件
B)在配置完vivi之后,则可以使用下面的命令进行编译:
$ make
C)编译成功之后,就可以在vivi目录下看到vivi生成的
二进制文件。
D)编译成功后,会在vivi目录下产生文件。
解释:
做完A)以后,会在根目录下生成.config文件(演示:
ls –a),这个文件是很多的宏,比如(演示):
CONFIG_MTD_NAND=y,这样就决定了哪些代码(功
能)将会被编译。
做完B)以后,将根据Makefile的第一个目标(do-it-all)来
生成vivi目标。
做完C)以后,系统将会调用$(OBJCOPY) -O binary -S
vivi-elf vivi $(OBJCOPYFLAGS)这句话生成二进制的vivi
文件。
做完D)后,系统会调用$(NM) -v -l vivi-elf >
,生成map文件(演示),供调试使用。
vivi的link文件:
1) link文件是ld工具要调用的输入。它在链接时其作
用。它在运行时才起到效果。注意它和链接不是一个
过程,它只是链接的一个过程,叫做‘运行导入过程
’。
2)vivi的link文件如下:
SECTIONS {
. = 0x33f00000;
.text : { *(.text) }
.data ALIGN(4) : { *(.data) }
.bss ALIGN(4) : { *(.bss) *(COMMON) }
}
3)以上内容的含义是:
a)它内存的视图。
b)它告诉ld,在装载时,vivi在内存的基地址是
0x33f00000,以后依次存放.text,.data和.bss。
c).data和.bss都是4位对齐(word对齐)。
d)我们可以看到这个配置文件没有指定.rodata的
load情况,也就是说它将由elf文件自身决定。
4) LINKFLAGS = -Tarch/ –Bstatic表明:
使用的导入内存的配置文件是,使用的是静态
的链接。
vivi代码导读
阶段1:arch/s3c2410/
沿着代码执行的顺序,完成如下
几件事情:
1、关WATCH DOG:上电后,WATCH
DOG默认是开着的。(P435,手册)
2、禁止所有中断:vivi中没用到中断(不过
这段代码实在多余,上电后中断默认是
关闭的) 。(P358)
vivi代码导读
3、初始化系统时钟:启动MPLL(machine PLL),
FCLK=200MHz(ARM920T),HCLK=100MHz(AHB
bus),PCLK=50MHz(APB bus),“CPU bus mode”改
为“Asynchronous bus mode”(使得FCLK和HCLK不
一样,也就是说CPU工作在200Mhz,AHB总线工作
在100Mhz)。(P226,总线仲裁器和CPU的工作频率
说明见ARM内核技术手册P108)。注意:这里没有提
到UPLL(也就是USB的PLL),在以后要使用USB设备
时会提到。
4、初始化内存控制寄存器。(注意:ENTRY(memsetup)
表示这个是一个内部符号,在本文件可以找到,和
GLOBLE相对) mem_cfg_val是一张表,表示内存访
问的控制,主要包括bank的属性和时序
vivi代码导读
5、检查是否从掉电模式唤醒,若是,则调用
WakeupStart函数进行处理——这是一段没用
上的代码,vivi不可能进入掉电模式。
6、点亮所有LED。
7、初始化UART0:
a.设置GPIO,选择UART0使用的引脚;
b.初始化UART0,设置工作方式(不使用
FIFO)(P313,UART工作模式)、波特率
115200(P309,波特律) 8N1(P312, UART的线控
)、无流控等。
8、将vivi所有代码(包括阶段1和阶段2)从nand flash复
制到SDRAM中:
a.设置nand flash控制寄存器;
b.设置堆栈指针——调用C函数时必须先设置堆栈;
c.设置即将调用的函数nand_read_ll的参数:r0=目
的地址(SDRAM的地址),r1=源地址(nand flash的
地址),r2=复制的长度(以字节为单位);注意,这
里我们要看一下S3C2410A user’s manual的P215,我
们采用Nand flash booting 方式,nand flash的范围是0
-0x4000000
vivi代码导读
d. 调用nand_read_ll进行复制;
e.进行一些检查工作:上电后nand flash最开始的4K
代码被自动复制到一个称为“Steppingstone”的内部
RAM中(地址为0x00000000-0x00001000);在执行
nand_read_ll之后,这4K代码同样被复制到SDRAM中
(地址为0x33f00000-0x33f01000)。比较这两处的
4K代码,如果不同则表示出错。
跳到bootloader的阶段2运行——就是调用init/中的
main函数
a.重新设置堆栈;
b.设置main函数的参数;
c.调用main函数。
vivi代码导读
当执行完的代码后,内存的使用情况如下:
vivi代码导读
阶段2:init/
本阶段从init/中的main函数开始执
行,它可以分为8个步骤。下面先把main
函数的代码罗列如下,然后逐个分析:
1、Step 1:reset_handler()
reset_handler用于将内存清零,代码在
lib/中。
vivi代码导读
2、Step 2:board_init()
board_init调用2个函数用于初始化定时器和设
置各GPIO引脚功能,代码在
arch/s3c2410/中:
init_time()只是简单的令寄存器TCFG0 = 0xf00
,vivi未使用定时器,这个函数可以忽略。
set_gpios()用于选择GPA-GPH端口各引脚的功
能及是否使用各引脚的内部上拉电阻,并设置
外部中断源寄存器EXTINT0-2(vivi中未使用外
部中断)。
vivi代码导读
3、Step 3:建立页表和启动MMU
mem_map_init函数用于建立页表,vivi使用段式页表,只需要一
级页表。它调用3个函数,代码在arch/s3c2410/中:
第9、10行的两个函数可以不用管它,他们做的事情在下面的
mmu_init函数里又重复了一遍。对于本例,在.config中定义了
CONFIG_S3C2410_NAND_BOOT。mem_map_nand_boot()函数
调用mem_mapping_linear()函数来最终完成建立页表的工作。页
表存放在SDRAM物理地址0x33dfc000开始处,共16K:一个页表
项4字节,共有4096个页表项;每个页表项对应1M地址空间,共
4G。mem_map_init先将4G虚拟地址映射到相同的物理地址上
(不使用cache,不使用write buffer)——这样,对寄存器的操作跟
未启动MMU时是一样的;再将SDRAM对应的64M空间的页表项修
改为使用cache。
vivi代码导读
mmu_init()函数用于启动MMU,它直接
调用arm920_setup()函数。arm920_setup
()的代码在arch/s3c2410/中
vivi代码导读
到此为止,内存的状态是:
vivi代码导读
4、Step 4:heap_init()
heap——堆,内存动态分配函数mmalloc就是从heap
中划出一块空闲内存的,mfree则将动态分配的某块内
存释放回heap中。
heap_init函数在SDRAM中指定了一块1M大小的内存作
为heap(起始地址HEAP_BASE = 0x33e00000),并在
heap的开头定义了一个数据结构blockhead——事实上,
heap就是使用一系列的blockhead数据结构来描述和操
作的。每个blockhead数据结构对应着一块heap内存,
假设一个blockhead数据结构的存放位置为A,则它对
应的可分配内存地址为“A + sizeof(blockhead)”到“A
+ sizeof(blockhead) + size – 1”。blockhead数据结构
在lib/中定义
vivi代码导读
现在来看看heap是如何运作的。vivi对heap的操作比较简
单,vivi中有一个全局变量static blockhead
*gHeapBase,它是heap的链表头指针,通过它可以
遍历所有blockhead数据结构。假设需要动态申请一
块sizeA大小的内存,则mmalloc函数从gHeapBase开
始搜索blockhead数据结构,如果发现某个blockhead
满足:
a. allocated = 0 //表示未分配
b. size > sizeA,
则找到了合适的blockhead,于是进行如下操作:
a.allocated设为1
b.如果size – sizeA > sizeof(blockhead),则将剩下
的内存组织成一个新的blockhead,放入链表中
c.返回分配的内存的首地址
vivi代码导读
释放内存的操作更简单,直接将要释放
的内存对应的blockhead数据结构的
allocated设为0即可。(对应的是mfree函
数)
vivi代码导读
下面简单演示先分配1K内存,再分配2K内存的过程:
vivi代码导读
heap_init函数直接调用mmalloc_init函数
进行初始化,此函数代码在lib/中,
比较简单,初始化gHeapBase即可
vivi代码导读
分配heap区域后,内存划分情况如下:
vivi代码导读
5、Step 5:mtd_dev_init()
在分析代码前先介绍一下MTD(Memory Technology
Device)相关的技术。在linux系统中,我们通常会用到
不同的存储设备,特别是FLASH设备。为了在使用新
的存储设备时,我们能更简便地提供它的驱动程序,
在上层应用和硬件驱动的中间,抽象出MTD设备层。
驱动层不必关心存储的数据格式如何,比如是FAT32、
ETX2还是FFS2或其它。它仅仅提供一些简单的接口,
比如读写、擦除及查询。如何组织数据,则是上层应
用的事情。MTD层将驱动层提供的函数封装起来,向
上层提供统一的接口。这样,上层即可专注于文件系
统的实现,而不必关心存储设备的具体操作。
vivi代码导读
在我们即将看到的代码中,使用mtd_info数据结构表
示一个MTD设备,使用nand_chip数据结构表示一个
nand flash芯片。
在mtd_info结构中,对nand_flash结构作了封装,向上
层提供统一的接口。比如,它根据nand_flash提供的
read_data(读一个字节)、read_addr(发送要读的扇区
的地址)等函数,构造了一个通用的读函数read,将此
函数的指针作为自己的一个成员。而上层要读写flash
时,执行mtd_info中的read、write函数即可。
在nand_chip结构中,使用this指针对自己进行一些和
Nand flash特性有关的特殊操作,很有特色。
问题:
在中,我们有如下结构:
struct nand_chip {
#ifdef CONFIG_MTD_NANDY
void (*hwcontrol)(int cmd);
void (*write_cmd)(u_char val);
void (*write_addr)(u_char val);
…
}
请问:void (*hwcontrol)(int cmd);占有多少空间?
vivi代码导读
mtd_dev_init()用来扫描所使用的NAND
Flash的型号,构造MTD设备,即构造一
个mtd_info的数据结构。对于本例,它
直接调用mtd_init(),mtd_init又调用smc_init
(),此函数在
drivers/mtd/maps/中, smc_init
()是最重要的函数,其中的this指针是它
最精妙的使用之处(详细分析)
MTD设备的树形结构:
MTDmtd[1]struct one
|
mtd[2]struct two
|
mtd[3]struct three
每一个MTD设备的this指针都是自己nand_chip
结构的地址。
vivi代码导读
6、Step 6:init_priv_data()
此函数将启动内核的命令参数取出,存放在内存特定的位置中。
这些参数来源有两个:vivi预设的默认参数,用户设置的参数(存
放在nand flash上)。init_priv_data先读出默认参数,存放在
“VIVI_PRIV_RAM_BASE”开始的内存上;然后读取用户参数,若
成功则用用户参数覆盖默认参数,否则使用默认参数。
init_priv_data函数分别调用get_default_priv_data函数和
load_saved_priv_data函数来读取默认参数和用户参数。这些参
数分为3类:
a.vivi自身使用的一些参数,比如传输文件时的使用的协议等;
b.linux启动命令;
c.nand flash的分区参数。
vivi代码导读
get_default_priv_data函数比较简单,它将vivi中存储这些默认参数的变量,复制到指
定内存中。此函数执行完毕后,内存使用情况如下:
vivi代码导读
7、Step 7:misc()和init_builtin_cmds()
这两个函数都是简单地调用add_command函
数,给一些命令增加相应的处理函数。在vivi
启动后,可以进去操作界面,这些命令,就是
供用户使用的。它们增加了如下命令:
a. add_command(&cpu_cmd)
b. add_command(&bon_cmd)
c. add_command(&reset_cmd)
d. add_command(¶m_cmd)
vivi代码导读
e. add_command(&part_cmd)
f. add_command(&mem_cmd)
g. add_command(&load_cmd)
h. add_command(&go_cmd)
i. add_command(&dump_cmd)
j. add_command(&call_cmd)
k. add_command(&boot_cmd)
l. add_command(&help_cmd)
vivi代码导读
8、Step 8:boot_or_vivi()
此函数根据情况,或者启动“vivi_shell”,进入与
用户进行交互的界面,或者直接启动linux内核。
(见129页)第10行等待键盘输入,如果在一段时间内键
盘无输入,或者输入了回车键,则调用run_autoboot
启动内核;否则调用vivi_shell进入交互界面。
vivi_shell等待用户输入命令(等待串口数据),然后根
据命令查找“Step 7:misc()和init_builtin_cmds()”设
置的命令链表,运行找到的命令函数。vivi_shell函数
是通过调用serial_term函数来实现的。
vivi代码导读
vivi执行完毕后,内存使用情况如下:
vivi代码导读
至此,linux内核终
于开始运行了!
PART III
VIVI 基本命令介绍
命令介绍
cpu [{cmds}] -- Manage cpu clocks
bon [{cmds}] -- Manage the bon file
system
reset -- Reset the system
param [set|show|save|reset] -- set/get parameter
part [add|del|show|reset] -- Manage MTD partitions
mem [{cmds}] -- Manage Memory
load {...} -- Load a file to RAM/Flash
go <addr> <a0> <a1> <a2> <a3> -- jump to <addr>
dump <addr> <length> -- Display (hex dump) a
range of memory.
call <addr> <a0> <a1> <a2> <a3> -- jump_with_return to
<addr>
boot [{cmds}] -- Booting linux kernel
help [{cmds}] -- Help about help?
命令使用
cpu命令
演示:
vivi> cpu
Usage:
cpu info -- Display cpu informatin
cpu set <clock> <ratio> -- Change cpu clock and bus clock
vivi> cpu info
Processor Information (Revision: 0x41129200)
--------------------------------------------
Processor clock: 200000000 Hz
AHB bus clock : 100000000 Hz
APB bus clock : 50000000 Hz
Register values
MPLLCON: 0x0005c040 (MDIV: 0x005c, PDIV: 0x04, SDIV: 0x00)
CLKDIVN: 0x00000003
说明:
1) MDIV, PDIV, SDIV是MPLLCON寄存器的内容
(S3C2410A user’s manual, P237) ,它们是用来计算
PLL的值的,用计算得出的PLL值,可以知道PLL要稳定
在哪一个频率(S3C2410A user’s manual, P238)
2) CLKDIVN是0x00000003表示分频比是1:2:4 (外部倍
频稳定时钟:总线频率:CPU频率)。
3) cpu set 命令其实是通过改变寄存器来改变CPU和总
线运行频率。
bon命令
演示:vivi> bon
Usage:
bon part info
bon part <offsets>
vivi> bon part info
BON info. (3 partitions)
No: offset size flags bad
---------------------------------------------
0: 0x00000000 0x00030000 00000000 0 192k
1: 0x00030000 0x000d0000 00000000 0 832k
2: 0x00100000 0x03efc000 00000000 0 62M+1008k
演示:
vivi> bon part 0 192k 1m
doing partition
offset = 0
flag = 0
offset = 196608
flag = 0
offset = 1048576
flag = 0
check bad block
part = 0 end = 196608
part = 1 end = 1048576
part = 2 end = 67108864
part0:
offset = 0
size = 196608
bad_block = 0
part1:
offset = 196608
size = 851968
bad_block = 0
part2:
offset = 1048576
size = 66043904
bad_block = 0
说明(结合程序):
1) bon命令是调用mtd设备驱动的应用程序。
2) bon part info是用来读取分区表的。
3) bon part 0 192k 1m是用来将nand flash设
成192K, 832K, 余下部分,这样三个分区。
go命令和call命令的区别:
1)go命令不保存lr,运行后不再返回。
2)go命令需要禁止MMU,清除TLB,原因
是go不再返回,系统原来在mmu和tlb中
的内容已经没有作用。
存取相关命令mem和dump:
演示:vivi> mem
invalid 'mem' command: wrong argumets
Usage:
compare <dst> <src> <length> -- compare
mem copy <dst> <src> <length>
mem info
mem reset -- reset memory control register
mem serach <start_addr> <end_addr> <value> --
serach memory address that cont
ain value
说明:
1)mem主要是用来比较,拷贝,设置存储控制寄存器,查找内
存值的。
2)演示:
vivi> mem info
RAM Information:
Default ram size: 64M
Real ram size : 64M
Free memory : 61M
RAM mapped to: 0x30000000 - 0x34000000
Flash memory mapped to : 0x10000000 - 0x12000000
Available memory region : 0x30000000 - 0x33de8000
Stack base address : 0x33defffc
Current stack pointer : 0x33defc7c
3)演示:
vivi> dump 0x33000000
33000000: FF FF FF FF FB FF FF FF-FF FF FF FB FF FF FF FD |
................
vivi> dump 0x30000000
30000000: 4B BF 9C 2B AB 9E BC 21-5B 56 9D 38 18 4D CB A5 |
K..+...![..
dump只是简单的用于显示存储器的内容。
问题:这里0x33000000为什么是这样的内容?
load命令
1) load的作用主要是目标板接受主机的
文件。
2) 演示:load ram vivi x
3) 使用xmodem串行通信协议,用于控
制重传和检错。
4) 还可以选用ymodem协议,这主要是
增加了MD5加密功能。
问题:如果我load一个程序到内存
中,能不能在屏幕上打出hello这句话?
注:演示:程序。
PART IV
本章小结
本章主要介绍了ARM下bootloader的移植原理。
在本章的开始部分,我们首先介绍了一般的
bootloader启动过程,它具有极强的指导意义。
随后,通过对vivi的介绍和代码导读,向读者
展示了vivi的启动过程。可以看到,vivi的编写
是符合一般bootloader的框架的。通过对本章
的学习,读者应该能够对vivi的启动过程有了
深入的认识,并可以根据单板硬件的配置对
vivi进行移植工作。