吴 磊 皮 智 袁宗胜
(北方工业大学计算机学院 北京 100144)
一种基于S3C6410的BootLoader的设计与实现
吴磊皮智袁宗胜
(北方工业大学计算机学院北京 100144)
引导加载程序(BootLoader)是嵌入式系统开发的关键技术之一,主要用于建立操作系统的运行环境。针对BootLoader严重依赖于硬件实现的问题,提出一种基于“NAND Flash+TFTP”存储下载方式的BootLoader设计方法,并采用OK6410开发板对该BootLoader进行实验验证。实验结果表明该BootLoader运行良好且稳定。该设计方法可以广泛地应用到嵌入式系统和其他的处理器中。
引导加载程序嵌入式系统S3C6410
嵌入式系统是以应用为中心,以计算机技术为基础,软硬件可裁剪,适应于对功能、可靠性、成本、体积和功耗等严格要求的专用计算机系统[1]。在工业控制、智能仪器、医疗设备和机器人等方面得到广泛的应用,而在嵌入式系统开发设计当中,尤为重要的就是引导加载程序的设计。
嵌入式系统从软件操作方面分析,一般分为四个层次: 引导加载程序、系统内核、文件系统和应用程序。其中引导加载程序就是BootLoader,它是嵌入式系统开发的难点之一,同时也是嵌入式系统运行的一个基本前提,没有这段与硬件紧密相连的代码,再强大的内核也发挥不了作用[3]。
1.1硬件结构简介
本BootLoader硬件实验环境采用飞凌公司的OK6410开发板,它采用了三星公司的ARM11处理器S3C6410。S3C6410是一款高性价比和低功耗的RSIC处理器,具备视频图像处理能力。并且能够稳定地运行于667MHz的主频以上,支持Mobile DDR和多种NAND Flash。OK6410开发板上集成了多种高端接口,如USB、SD卡、液晶屏和以太网等,主要硬件构成如图1所示。
图1 主要硬件构成
1.2BootLoader开发方法与工具
采用交叉开发模式,即程序的编写和编译在装有Red Hat Enterprise Linux6.3的PC宿主机上完成,而交叉编译产生的bin文件在OK6410目标板上运行。其中选用arm-linux-gcc-4.3.2工具链作为开发工具,它包括了编译器、链接器和汇编器等开发工具[5-7],使用Makefile文件来管理整个工程,并选用JTAG进行下载并调试BootLoader,另外选用交叉网线来下载嵌入式linux内核映像文件(zImage),系统开发框图如图2所示。
图2 系统开发框图
2.1S3C6410启动流程
S3C6410的启动流程如图3所示。
图3 S3C6410启动流程
S3C6410的启动流程是IROM初始化,IROM中固化了软件,称为BootLoader0,是0阶段的BootLoader,该BL0 执行初始化时钟,D-TCM,设备特殊控制器,引导设备;加载BL1到Stepping Stone(垫脚石),将放在nandflash中的BootLoader1(即 BootLoader最前面的8K)拷贝到Stepping Stone中;执行BL1:BL1初始化系统时钟,UART,SDRAM,Stepping Stone执行完8K BootLoader后,将剩余的BootLoader(BL2)拷贝到SDRAM中运行;执行BL2:跳转到SDRAM中执行BL2,加载内核。
2.2BootLoader的总体设计
在嵌入式操作系统中,BootLoader运行在操作系统内核之前。主要可以初始化一些相关的硬件设备、建立内存空间的映射图,将系统的软硬件环境带到一个合适状态,为最终调用操作系统内核准备好正确的环境[2-4]。
BootLoader的启动通常可以分为第一阶段和第二阶段,第一个阶段主要包含依赖于CPU的系统结构,比如设备初始化代码等,通常都用汇编语言来实现。第二阶段通常用C语言来实现,以便实现更复杂的功能,也能使程序有更好的可读性和可移植性,主要任务有一些相关硬件初始化,检测系统的内存映射和加载linux内核等工作。BootLoader的总体设计流程如图4所示。
图4 BootLoader的总体设计流程图
3.1核心初始化
核心初始化阶段的任务主要包括异常向量表,设置svc模式,关闭看门狗,关闭中断和外设基地址初始化。在ARM体系结构中,异常向量表放在上电后映射在从0x00000000开始的32个字节的连续物理地址上,它的作用是指定了各种异常处理程序的入口地址[2]。因为每个异常只对应4个字节,不能放下整个处理程序,用来跳转到相应程序。具体代码如下:
b reset
//复位异常向量,地址为0x00000000
ldr pc,_undefined_instruction
//未定义指令异常,跳转至未定义异常服务程序
ldr pc,_software_interrupt
//软中断异常,跳转至软中断异常服务程序
ldr pc,_prefetch_interrupt
//指令预取中止异常,跳转至指令异常预取中止异常服务程序
ldr pc,_data_abort
//数据访问中止异常,跳转至数据访问中止异常服务程序
ldr pc,_not_used
//保留,占用4个字节
ldr pc,_irq
//IRQ异常,跳转至普通中断服务程序
ldr pc,_fiq
//FIQ异常,跳转至快速中断异常服务程序
svc模式属于特权模式,可以访问一些受控资源,并且比sys模式还多了些自己模式下的影子寄存器。相对sys模式来说,可以访问资源的能力相同,但是拥有更多的硬件资源。从BootLoader方面考虑,设置svv模式为了初始化系统相关硬件资源和获取尽量多的权限。具体代码如下:
set_svc:
//设置svc模式
mrs r0,cpsr
bic r0,r0,#0x1f
//清除低5位
orr r0,r0,#0xd3
//设置为svc模式,并且屏蔽irq和fiq中断
msr cpsr,r0
mov pc,lr
//关闭看门狗
#define pwatchdog 0x7e004000
disable_watchdog:
ldr r0,=pwatchdog
//ldr这里是伪指令,将地址保存到r0中
mov r1,#0x0
str r1,[r0]
mov pc,lr
//关闭中断,将以下2个地址全部置1
disable_interrupt:
mvn r1,#0x0
ldr r0,=0x71200014
str r1,[r0]
ldr r0,=0x71300014
str r1,[r0]
mov pc,lr
//关闭mmu和cache
disable_mmu:
mcr p15,0,r0,c7,c7,0
//使Icache和Dcache失效
mrc p15,0,r0,c1,c0,0
//关闭Icache,Dcache和mmu
bic r0,r0,#0x00000007
mcr p15,0,r0,c1,c0,0
mov pc,lr
//外设基地址初始化
set_peri_port:
ldr r0, =0x70000000
orr r0, r0, #0x13
mcr p15,0,r0,c15,c2,4
mov pc, lr
3.2点亮LED
点亮LED是为了调试代码用。具体代码如下:
#define GPMCON 0x7F008820
//这里的地址根据开发板硬件资源的不同而设置不同
#define GPMDAT 0x7F008824
light_led:
ldr r1,=GPMCON
//控制寄存器
ldr r0,=0x1111
str r0,[r1]
ldr r1,=GPMDAT
//数据寄存器
ldr r0,=0xe
//点亮一个led灯,观察调试使用
str r0,[r1]
mov pc,lr
3.3时钟初始化
S3C6410可以使用外部晶振和外部时钟两种方式输入时钟信号,默认的工作主频为12 MHz(晶振频率),S3C6410有三个PLL,分别为APLL、MPLL和EPLL。其中APLL产生ACLK,给ARM core使用。MPLL产生HCLK和PCLK。EPLL产生特殊的时钟,比如为USB提供48MHz时钟。时钟初始化流程:(1)设置lock time(不需要设置,保持默认值即可);(2)设置分频系数;(3)设置CPU到异步工作模式;(4)设置FCLK。具体代码如下:
//设置分频系数
#define CLK_DIV 0 0x7e00f020
#define DIV_VAL (0x1<<0)|(0x1<<9)|(0x1<<8)|(0x3<<12)
//设置为66MHz
clock_init:
ldr r0,=CLK_DIV0
ldr r1,= DIV_VAL
str r1,[r0]
mov pc,lr
//设置CPU为异步模式
//这里可以直接设置OTHERS寄存器来完成设置异步模式
#define OTHERS 0x7e00f900
ldr r0,=OTHERS
ldr r1,[r0]
bic r1,r1,#0xc0 //这里将第六位也设置为0的主要目的是当
//选择为异步模式的时候,应选取MPLL的输出
str r1,[r0]
//设置FCL
#define APLL_CON 0x7e00f00c
#define MPLL_CON 0x7e00f010
#define PLL_VAL ((1<<31)|(266<<16)|(3<<8)|(1<<0))
ldr r0,=APLL_CON
ldr r1,=PLL_VAL
str r1,[r0]
ldr r0,=MPLL_CON
ldr r1,=PLL_VAL
str r1,[r0]
//为了必须保证设置APLL和MPLL作为输出,这里还需要设置
//CLK_SRC的低2位为1
#define CLK_SRC 0x7e00f01c
ldr r0,=CLK_SRC
mov r1,#0x3
str r1,[r0]
3.4内存初始化
S3C6410处理器拥有32位地址总线,其寻址空间为4 GB。其中高2 GB为保留区,低2 GB区域又可划分为2部分:主存储区和外设区。其地址空间和主存储区如图5所示。
图5 地址空间和主存储区
内存初始化的这一过程主要是指对 DRAM、FLASH 的存储设备的地址范围、数据宽度以及 DRAM 的刷新率进行设备,芯片不同,设置也不同[8-10]。
3.5将nandflash中的BootLoader复制至内存
OK6410开发板上电之后,会运行SROM中的BootLoader0(由三星公司出厂设置),由S3C6410启动流程图3可以看出,S3C6410的Stepping Stone(垫脚石)只有8 KB,由于8 KB容量太小,不足以完成第二阶段的任务,因此需要借助这8 KB的BootLoader1将nandflash中的所有的BootLoader复制到内存运行。将nandflash中的BootLoader复制至内存的具体代码如下:
copy_to_ram:
ldr r0,=0x0c000000
//起点
ldr r1,=0x50008000
//终点
add r3,r0,#1024*8
//复制8KB
copy_loop:
//循环复制至0x50008000
ldr r2,[r0],#4
str r2,[r1],#4
cmp r0,r3
bne copy_loop
mov pc,lr
3.6C语言环境初始化
OK6410的内存大小是256 MB,64 MB的内存对于一个精简通用的BootLoader足够使用,即设置堆栈寄存器(SP)的地址为0x50000000+64 MB = 0x54000000,栈初始化的代码如下:
init_stack:
ldr sp,=0x54000000
mov pc,lr
BSS(Block Started by Symbol)通常是指用来存放程序的未初始化的全局变量和静态变量的一块内存区域。BSS段初始化就是给该段清零,方便以后使用BSS段的数据,当定义变量时,就会初始化该变量为零。具体代码如下:
//初始化BSS段
clear_bss:
ldr r0,=bss_start
//BSS起始地址
ldr r1,=bss_end
//BSS终止地址
cmp r0,r1
moveq pc,lr
clean_loop:
//循环清除
mov r2,#0;
str r2,[r0],#4
cmp r0,r1
bne clean_loop
mov pc,lr
//跳转至C代码
从SRAM调到内存中,直接使用绝对跳转方式。需要在myboot工程下面新建一个main.c文件,具体代码如下:
int myboot_main()
//main函数
{
return 0;
}
修改汇编start.S和Makefile文件,在原start.S里面的reset下添加ldr pc,=mymain 用来跳转至C语言环境,myboot_main是一个函数地址,在原Makefile文件的依赖文件里面添加main.o即可,通过在myboot_main中添加点亮LED的代码,测试LED点亮,说明程序已经跳转至myboot_main。
4.1MMU初始化
在BootLoader第一阶段的设计中关闭了MMU,因此物理地址和虚拟地址是相等的,在Linux系统中不管是内核还是应用程序,都是使用的虚拟地址,而MMU的作用是自动进行虚拟地址到物理地址的转化,需要找到一级页表,而一级页表的基地址保存在CP15的C2寄存器中。本BootLoader按照段式方式来进行映射,即一级页表项中最后两位为‘10’。段式方式转换如图6所示,由高12位的段基址和低20位的偏移地址组成。
图6 段式方式转换
MMU的初始化具体代码如下:
void enable_mmu()
//使能MMU
void creat_page_table()
//创建页表和内存映射
{
unsigned long* ttb = (unsigned long*)0x50000000;
//页表存放在内存起始地址
unsigned long vaddr,paddr;
vaddr = 0xA0000000;
//虚拟地址
paddr = 0x7F000000;
//物理地址
*(ttb + (vaddr>>20)) = (paddr&0xfff00000) | (MMU_SECDESC);
vaddr = 0x50000000;
paddr = 0x50000000;
while(vaddr < 0x54000000) //内存映射64MB,地址范围是
//(0x50000000-0x54000000)
{
*(ttb + (vaddr>>20)) = (paddr&0xfff00000) | (MMU_SECDESC_WB);
vaddr += 0x100000;
paddr += 0x100000;
}
}
由于MMU初始化后,对应需要修改LED的控制寄存器和数据寄存器的地址,具体代码如下:
#define GPKCON (volatile unsigned long*)0xA0008820
//使用的虚拟地址
#define GPKDAT (volatile unsigned long*)0xA0008824
//使用的虚拟地址
*(GPKCON) = 0x1111;
*(GPKDAT) = 0xe;
通过观察LED来验证MMU初始化的代码。如果LED能亮起来,说明MMU这部分代码没有问题;否则,MMU不起作用。
4.2相关硬件初始化
在BootLoader第一阶段中,ARM设置在svc的模式下,如果在相关硬件初始化的过程中需要用到中断的时候,须将ARM设置为irq模式,因此需要重新设置堆栈。具体代码如下:
//设置堆栈
init_stack:
msr cpsr_c, #0xd2
ldr sp, =0x53000000
//初始化r13_irq 中断响应的地址
msr cpsr_c, #0xd3
ldr sp, =0x54000000
//初始化r13_svc
mov pc,lr
针对硬件资源不同的开发板,针对性地做一些硬件的初始化,这里主要包括以下几点。
(1) 串口控制台建立,在本BootLoader中,需要用户自己手动在串口控制台输入选择项来下载Linux内核,通过串口来交互,移植printf和scanf函数等。
(2) DMA解决了CPU每次从内存当中拷贝很大的数据到串口,提高CPU的效率,可以初始化DMA。
(3) 硬件资源还包括有LCD(OK6410用的TFT液晶屏)和触摸屏(OK6410用的电阻屏),根据不同的硬件资源做一些适当的修改。
4.3网卡搭建
根据硬件资源的不同,初始化DM9000的代码也有所不同,本BootLoader需要通过交叉网线来下载Linux内核,通过ARP协议的实现来测试DM9000是否正常工作。
声明以太网包和ARP包的结构体代码如下:
typedef struct eth_hdr
//以太网包
{
u8 d_mac[6];
//目的MAC地址
u8 s_mac[6];
//源MAC地址
u16 type;
//类型
}ETH_HDR;
typedef struct arp_hdr
//ARP包
{
ETH_HDR ethhdr;
//以太网包
u16 hwtype;
//硬件类型
u16 protocol;
//协议类型
u8 hwlen;
//硬件地址长度
u8 protolen;
//协议地址长度
u16 opcode;
//OP=1 表示ARP请求 OP=2 表示ARP应答
u8 smac[6];
//发送端以太网地址
u8 sipaddr[4];
//发送端IP地址
u8 dmac[6];
//目的以太网地址
u8 dipaddr[4];
//目的IP地址
}ARP_HDR;
主要的函数有2个如下:
void arp_request(); //发送ARP请求包,注意网络中的字节序问
//题,网络字节序是大端传输
u8 arp_process(u8 *buf, u32 len);
//解析ARP应答包,提取MAC地址
测试网卡的搭建如下,开发板OK6410通过向宿主机发送ARP请求,宿主机回复一个ARP应答包,开发板即能知道宿主机的MAC地址,流程如图7所示。
图7 ARP通信流程
开发板OK6410的IP地址设置为10.5.114.107,MAC地址设置为09:08:07:06:05:04,宿主机的IP地址和物理地址通过ifconfig命令可以查看如图8所示:IP地址为10.5.114.109,MAC地址为00:0C:29:45:FC:6A。
图8 宿主机PC的IP地址和MAC地址
实验下载该BootLoader,从nandflash启动,OK6410循环发送ARP请求,显示数据如图9所示:MAC地址的显示和图7中SecureCRT打印的结果一致,实验说明OK6410开发板可以正确获取到宿主机的MAC地址。
图9 SecureCRT串口显示宿主机PC的IP和MAC
通过Wireshark抓包如图10所示:Wireshark正确抓到ARP的包正确,说明OK6410能正确获取到宿主机PC的MAC地址,验证了网卡搭建的正确性。
图10 Wireshark抓取的ARP包
4.4移植TFTP客户端
通过TFTP客户端下载linux内核至开发板OK6410,在开发板下载之前发送一个ARP请求来获取宿主机PC的MAC地址。TFTP的通信流程如图11所示。
图11 TFTP通信流程
声明IP报文、UDP包以及TFTP包的结构体代码如下:
typedef struct ip_hdr
//IP报文
{
ETH_HDR ethhdr;
//以太网包
u8 vhl;
//版本和首部长度
u8 tos;
//服务级别
u16 len;
//报文长度
u16 ipid;
//标识
u16 ipoffset;
//片位移
u8 ttl;
//生存时间
u8 proto;
//上一层协议类型
u16 ipchksum;
//校验和
u8 srcipaddr[4];
//源IP地址
u8 destipaddr[4];
//目的IP地址
}IP_HDR;
typedef struct udp_hdr
//UDP包
{
IP_HDR iphdr;
//IP报文
u16 sport;
//源端口号
u16 dport;
//目的端口号
u16 len;
//长度
u16 udpchksum;
//校验和
}UDP_HDR;
typedef struct tftp_package
{
u16 opcode;
//操作码
u16 blocknum;
//块编号
u8 data[0];
//数据
}TFTP_PAK;
主要的函数有:
void tftp_send_request(const char *filename);
//用来发送TFTP请求
void tftp_send_ack(u16 blocknum);
//用来发送TFTP ACK
void tftp_process(u8 *buf, u32 len, u16 port);
//处理TFTP请求
TFTP测试结果如下:在Linux宿主机上面配置好TFTP服务器,OK6410开发板通过交叉网线下载zImage(zImage存放在linux宿主机上面配置好的TFTP服务器的目录下)文件,SecureCRT串口显示如图12所示和 Wireshark抓包如图13所示。
图12 SecureCRT串口显示TFTP下载完成
图13 Wireshark抓取的TFTP包
通过SecureCRT串口显示和wireshark抓包显示可以看出OK6410开发板能正确发送TFTP请求,并且能正确解析和下载zImage文件,实验说明OK6410的TFTP客户端正确将Linux内核下载至nandflash。
4.5移植bootm命令
TFTP客户端下载完Linux内核后,需要启动该内核,实现一个命令来启动该Linux内核。这个bootm命令用于启动一个操作系统映像。它会从映像文件的头部取得一些信息,这些信息包括:映像文件的基于的CPU架构、其操作系统类型、映像的类型、压缩方式、映像文件在内存中的加载地址、映像文件运行的入口地址、映像文件名等。紧接着bootm将映像加载到指定的地址,跳转至入口地址进入Linux内核。主要的函数实现如下:
#define SDRAM_KERNEL_START 0x51000000 //linux内核起始地址
theKernel = (void (*)(int, int, unsigned int ))SDRAM_KERNEL_START;
//指向起始地址
void boot_linux();
//用来处理启动linux内核,这一过程需要设计
//启动参数,包括核心启动参数,内存参数,命令行参数和结束标志等
setup_core_tag();
//设置核心启动参数
setup_mem_tag();
//设置内存参数
setup_cmdline_tag();
//设置命令行参数
setup_end_tag();
//设置结束标志
4.6BootLoader测试
烧写BootLoader,然后从nandflash启动,TFTP客户端下载完Linux内核后,通过SecureCRT串口输入数字3,即可正确引导Linux内核和根文件系统。实验效果如图14、图15和图16所示。
图14 SecureCRT串口显示完成加载Linux
图15 SecureCRT串口显示获取MAC和TFTP下载正常
图16 SecureCRT串口显示正在启动Linux内核
通过图14、图15和图16显示,实验结果说明该BootLoader能正确引导Linux内核和根文件系统,并且运行稳定,为后续的嵌入式系统开发奠定了基础。
BootLoader是严重依赖于硬件而实现的,每种不同体系结构的处理器都有不同的BootLoader,因此BootLoader的设计在嵌入式系统开发中非常关键,如何设计出一个比较通用的BootLoader更是困难。本文提出了一种基于“NAND Flash+TFTP”存储下载方式的BootLoader设计方法,实验结果表明该BootLoader运行稳定,只需要根据硬件资源的不同,稍作一些代码上的修改,即可移植至嵌入式系统和其他的处理器中。同时该设计方法为BootLoader系统设计提供了实用价值。
[1] 郑灵翔. 嵌入式系统设计与应用开发[M]. 北京: 北京航空航天大学出版社, 2006:201-219.
[2] 孙琼. 嵌入式Linux应用程序开发详解[M]. 北京:人民邮电出版社,2006:172-175.
[3] 田会峰. 基于S3C2440的BootLoader设计与实现[J]. 自动化技术与应用, 2010,29(7):29-32.
[4] 杜春雷. ARM体系结构与编程[M]. 北京:清华大学出版社, 2003:22-223.
[5] 袁磊,朱怡安,兰婧. 嵌入式系统BootLoader设计与实现[J]. 计算机测量与控制,2009,17(2):389-391.
[6] 张群忠,沈建华. ARM&Linux嵌入式系统BootLoader的研究与设计[J]. 计算机应用与软件,2006,23(12):97-99.
[6] 叶茂,李智,任和. Cortex-A8的Bootloader设计与实现[J].单片机与嵌入式系统应用,2015,2(1):17-20.
[7] 冯林琳,耿恒山. 基于S3C6410的Uboot分析与移植[J]. 计算机与现代化,2013,3(1):119-121.
[9] 梁超,杨峰,雷鸣,等. U-Boot SD卡启动方式的移植分析与功能扩展[J]. 现代电子技术,2013,36(20):84-86,90.
[10] 吴伟,周延周. 基于S3C2440的嵌入式系统小型U-Boot的研究[J]. 广东工业大学学报,2014,31(4):85-89.
DESIGN AND REALISATION OF AN S3C6410-BASED BOOTLOADER
Wu LeiPi ZhiYuan Zongsheng
(College of Computer, North China University of Technology, Beijing 100144, China)
BootLoader is one of the key technologies in embedded system development, and is mainly used for establishing operating system environment. To address the problem of BootLoader in seriously relying on the hardware implementation, in this paper we propose a BootLoader design method which is based on “NAND Flash+TFTP” storage download mode, and verify this BootLoader by an experiment with OK6410 development board. Experimental result shows that the newly designed BootLoader operates good and stably. The design method can be widely applied to embedded system and other processors.
BootLoaderEmbedded systemS3C6410
2015-06-08。北京市自然科学基金项目(4131001);中央支持地方专项(PXM2014_014212_000097);北京市属高等学校创新团队建设与教师职业发展计划项目(IDHT20130502);北京市自然科学基金项目(4132026)。吴磊,副教授,主研领域:嵌入式技术,无线通信。皮智,硕士生。袁宗胜,硕士生。
TP311.52
A
10.3969/j.issn.1000-386x.2016.09.057