在基于ARM内核的嵌入式处理器的板级支持包中,BootLoader是系统在上电过程中要首先执行的第一段代码,虽然BootLoader不是系统在启动过程中所必需的,但是它的存在可以对嵌入式产品的开发和调试带来很多的方便,例如:每次对操作系统镜像进行修改以后,可以以太网,串口的硬件端口将镜像下载到目标嵌入式设备中,比起每次修改以后就要重新烧写Flash要简便得多。
Windows CE BootLoader的软件框架主要可以分为如下5个部分
(1).BLCOMMON:BootLoader通用代码库,主要完成boot过程中与硬件无关的操作.这一部分不需要用户自己开发,它主要是导出与硬件相关的硬件函数接口供OEM代码实现,BLOCMMON代码主要完成解析操作系统镜像文件,对下载的镜像数据执行数据校验,跟踪下载进度等 (2)EBoot 支持代码:主要负责实现与以太网相关的函数,同BLCOMMON代码一样,也是实现与硬件无关的操作:负责实现dhcp,tftp,udp等网络协议的,但仅适用于Eboot的情况,并且要导出与硬件相关的函数供用户实现。
(3).Ethdbg 驱动程序:严格来说,Ethdbg驱动程序并不是严格的驱动程序,它只用来实现基本的网络端口操作Ethdbg驱动程序不仅要为BootLoader服务,还要为OAL(OEM逻辑代码层)模块的KITL(内核无关传输层)服务。
(4).OEM代码:这是需要用户开发的代码核心,负责实现BLCOMMON导出的与硬件相关的函数。主要完成BootLoader的拷贝,硬件平台初始化(MMU的初始化,虚拟内存页表的设置,填充向OAL层传递共享信息的数据结构等)
(5).BootPart支持库,向BootLoader提供存储分区,底层读写,擦除,格式化等存储设备操作。
系统上电第一步——Startup函数,Eboot sources文件中有一项配置”EXEENTRY=StartUp”正是用于配置启动过程中运行入口函数StartUp.
系统上电后第一步就是运行Startup函数的代码,这是一个汇编语言函数,主要其最主要功能是执行芯片级初始化:禁止中断,配置系统时钟频率,复制BootLoader镜像到内存,设置存储器的读写周期,构造内存映射表,启用MMU,并启用虚拟内存
Startup函数首先有两条重要的地址定义,定义ram空间的物理基地址和页表的基地址:这是startup函数主要操作的物理地址空间
PHYBASE EQU 0x30000000 ;ram空间的基地址
PTs EQU 0x30010000 ;用于存储页表的基地址
在进入入口函数以后首先通过对协处理器的操作来清空TLB,指令Cache和数据Cache
mcr p15, 0, r0, c8, c7, 0 ; flush both TLB
mcr p15, 0, r0, c7, c5, 0 ; invalidate instruction cache
mcr p15, 0, r0, c7, c6, 0 ; invalidate data cache
然后向I/O端口F控制寄存器写入0x55aa,使外部中断引脚EINT4-7设为输出,在main函数中OEMInitDebugLED函数将通过这四个引脚点亮LED灯.
Ldr r0,=GPFCON
Ldr r1,0x55aa
Str r1,[r0]
在startup中将设置禁止中断,使系统进入轮询模式,这主要是为了保证BootLoader顺序执行,简化了开发和理解难度,虽然损失了效率,但是在BootLoader运行过程中仅仅是引导操作系统加载,不会用到很多的外设,很明显,这是利大于弊的,通过向INTMSK寄存器写入相应的位来禁止中断,除此之外还有INTSUBMSK(中断子屏蔽寄存器)及看门狗中断(别忘了哦!)将所有的中断设置为外部中断(此时已经被屏蔽):
ldr r0, = INTMSK
ldr r1, = 0xffffffff ; disable all interrupts
str r1, [r0]
ldr r0, = INTSUBMSK
ldr r1, = 0x7fff ; disable all sub interrupt
str r1, [r0]
ldr r0, = INTMOD
mov r1, #0x0 ; set all interrupt as IRQ
str r1, [r0]
禁止所有中断后开始配置系统时钟:
Ldr r0, = CLKDIVN
Ldr r1,0x7
Str r1, [r0]
CLNDIVN(时钟分频寄存器)负责设置UCLK( USB总线时钟),FCLK与HCLK(AHB总线时钟)的比例,HCLK与PCLK(APB总线时钟的比例)详情可以参考s3c2440A文档第七章
ldr r0, = MPLLCON
ldr r1, = PLLVAL
str r1, [r0]
MPLLCON设置主时钟的频率:具体计算如下:
Mpll = (2 * m * Fin) / (p * 2s)
m = (MDIV + 8), p = (PDIV + 2), s = SDIV
UPLLCON设置USB总线时钟频率,S3C2440a的文档列出了一些参考值,有兴趣可以去深入了解一下
ldr r0, = UPLLCON
ldr r1, = ((0x3c << 12) + (0x4 << 4) + 0x2)
Upll = (m * Fin) / (p * 2s)
m = (MDIV + 8), p = (PDIV + 2), s = SDIV
MDIV的值为MPLLCON的第12~19位,PDIV为MPLLCON的第4~9位,SDIV为第0~3位的值
搞定了时钟设置之后当然就是初始化内存控制器,为后面一系列的内存操作作准备:内存控制器的初始化其实就是设置访问的周期,访问字节宽度,访问中地址有效时间,等待时间,片选时长等,把要初始化的值建立成一张有序的内存控制表(MEMCTRLTAB),然后再存入相应的寄存器中会让汇编代码简洁,有较好的可读性(本来读汇编代码是很烦人的),程序一目了然:
add r0, pc, #MEMCTRLTAB - (. + 8)
ldr r1, = BWSCON
add r2, r0, #52
40 ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne %B40
其中BSWCON就是存储器寄存器的开始地址, 地址为0x48000000,后面的寄存器地址都是连续的4字节地址.这样内存空间就可以正常的访问了,前面的所有工作都是为了在内存空间内运行起来BootLoader作准备的,之后就是从Flash中拷贝BootLoader到Ram内存,然后使之运行起来了,当然,如果你的Flash足够大(这是很要成本的),能够让BootLoader流畅作XIP(execute in place)运行,也不一定要拷贝这个过程,在拷贝之前,还要检查当前是不是在Flash中运行,避免重复拷贝,检查代码如下:
Ands r9 ,pc #0xff000000
Beq %F20
为什么要检查pc寄存器呢?是因为S3C2440的编址规定,Nor Flash是编址到Bank1从0开始的地址,而用作内存的sdram只能编址在Bank6-Band7从0x30000000的空间中,故有此项判断!
拷贝过程只需确定拷贝的源地址,目的地址,源地址可以确定就是从0地址,而目的地址却不能随意确定,需要有据可循,其依据就是全局内存映射表g_oalAddressTable和boot.bib文件中的定义:
boot.bib中有一项:
EBOOT 8c038000 00040000 RAMIMAGE//指定的Eboot在Ram中的虚拟地址,
运行到这里依旧使用的是物理地址,在g_oalAddressTable中定义映射到0x80000000的物理空间是0x32000000
g_oalAddressTable中与此段内存相关的定义为:
DCD 0x8C000000, 0x30000000, 32 ; 32 MB DRAM BANK 6
因此拷贝的目的地址起始值就是0x30038000:
拷贝代码如下:
ldr r0, = 0x38000
add r0, r0, #PHYBASE //PHYBASE=0x30000000
mov r1, r0 ; (r1) copy destination
ldr r2, =0x0 ; (r2) flash started at physical address 0
ldr r3, =0x10000 ; 拷贝计数,boot.bib文件定义的Eboot大小为0x40000,一次拷贝4个字节,所以计数为10000
10 ldr r4, [r2], #4
str r4, [r1], #4
subs r3, r3, #1
bne %b10
完成拷贝之后就要开始运行BootLoader了,方法很简单,即将拷贝的物理地址赋值给pc,
Mov pc,r0
这意味BootLoader又要重新开始运行了,但这次启动不会重复复制BootLoader镜像,而是完成最重要的工作:构造页表并启动MMU,微软Windows CE的BootLoader所实现的功能与特性之一就是BootLoader的硬件初始化代码与OAL代码共享,而OAL作为Windows CE的开始是需要启用虚拟内存并为MMU设置正确的页表,还有一点:指导Windows CE的编译系统产生二进制文件的.bib文件使用的内存地址都是使用的虚拟地址,这些虚拟地址是编译系统对二进制代码进行地址重定位的重要依据,定义内存映射表的依据就是全局内存映射表oemaddrtab_cfg.inc
首先获取内存映射表的地址和页表要存储的地址
20 add r11, pc, #g_oalAddressTable - (. + 8) ;
ldr r10, =PTs ; (r10) = 1st level page table
构造“cachable”映射表,构造完成以后会以页表中的虚拟地址+20000000(也就是A0000000-BFFFFFFF)重新构造“uncachable”页表,但是所对应的物理地址是相同的,Cachable与uncachable页表的区别就是在页表所存储内容的低位有一个cachable位标志
; (r10) = 1st level page table
; (r11) = ptr to MemoryMap array
add r10, r10, #0x2000 ; BootLoader所使用的虚拟内存映射表是以1MB为单位构建,构建的虚拟地址为(0x80000000-0x9FFFFFFF)共512MB的地址,每个页表项占用4字节的空间,共2KB的地址空间,存储页表的地址为0x30012000-0x300127FF
mov r0, #0x0E ; (r0) = PTE for 0: 1MB cachable bufferable
设置cachable位,每一个页表内容的高12(因为页表是以1MB为单位构建的)位是虚拟内存对应的物理内存段的起始地址,低20位用来存放相应虚拟及物理内存段的读写权限及可缓冲信息等属性信息
orr r0, r0, #0x400 ; 设置内核读写允许位
25 mov r1, r11 ; (r1) = 内存映射表所在的地址
获取内存映射表的内容:
30 ldr r2, [r1], #4 ; (r2) = virtual address to map Bank at
ldr r3, [r1], #4 ; (r3) = physical address to map from
ldr r4, [r1], #4 ; (r4) = num MB to map
cmp r4, #0
beq %f40 ;内存映射表的最后一个条目是0结束的,用于判断是否构造完成,如果构造完成则跳转到页表存储起始地址+800的地址中去重新构造“uncachable”映射表,
ldr r5, =0x1FF00000
and r2, r2, r5 ; 对虚拟地址进行512MB对齐,因为要映射到的虚拟地址对应的空间为0x80000000-0x9FFFFFFF,其大小不能超过512MB,r2的高14位用于指定虚拟地址对页表首地址的偏移量
ldr r5, =0xFFF00000
and r3, r3, r5 ; 对物理地址进行4GB对齐
add r2, r10, r2, LSR #18 ;(r2>>18)=页表的偏移地址,r10页表的起始地址
r2=页表的实际存储地址
add r0, r0, r3 ; r0=缓冲及读写属性(低20位)+物理内存段起始地址(高12位),r0即页表地址所存储的内容
35 str r0, [r2], #4 ;存储页表内容到页表地址中
add r0, r0, #0x00100000 ; (r0) = PTE for next physical page,指向下一个页表的起始物理地址,1MB单位
sub r4, r4, #1 ; Decrement number of MB left,剩余要的构造的页表数目
cmp r4, #0
bne %b35 ; 判断是否构造完成,如未构造完成,则继续构造,直到一个页表条目完成为止
bic r0, r0, #0xF0000000 ; Clear Section Base Address Field
bic r0, r0, #0x0FF00000 ; Clear Section Base Address Field,清除物理内存段地址域,r0在构造页表的过程中要作为存储页表内容的寄存器反复利用,每一个条目构造完成之后,都要清除段地址
b %b30 ; 获取内存映射表的下一个条目
下面的内容是构造uncachable页表,对应的虚拟地址均是cachable虚拟地址+20000000,物理地址不变
40 tst r0, #8
bic r0, r0, #0x0C ; 清除缓冲位,即页表内容的2,3位
add r10, r10, #0x0800 ; (r10) =uncachable映射的页表基地址,即30012800,即r10指向了0xa0000000虚拟地址对应的页表起始地址
bne %b25 ; go setup PTEs for uncached space,此时已经完成了g_oalAddressTable的cachable页表和uncachable页表映射
sub r10, r10, #0x3000 ; (r10) = restore address of 1st level page table
; Setup mmu to map (VA == 0) to (PA == 0x30000000).
ldr r0, =PTs ; PTE entry for VA = 0,创建从物理地址30000000到虚拟地址0的映射
ldr r1, =0x3000040E ; uncache/unbuffer/rw, PA base == 0x30000000,读写属性等信息,cachable,bufferable
str r1, [r0] ;存储页表内容
; uncached area.
add r0, r0, #0x0800 ; PTE entry for VA = 0x0200.0000 , uncached,对物理0地址被映射到虚拟地址0x02000000的uncachable起始地址
ldr r1, =0x30000402 ; uncache/unbuffer/rw, base == 0x30000000,uncachable,unbufferable
str r1, [r0] ;完成以后 虚拟地址0对应0x30000000的cachable,虚拟地址0x2000000对应0x30000000的uncached
; Comment:
; The following loop is to direct map RAM VA == PA. i.e.
; VA == 0x30XXXXXX => PA == 0x30XXXXXX for S3C2400
; Fill in 8 entries to have a direct mapping for DRAM
建立虚拟地址到相等的物理地址的映射
;
ldr r10, =PTs ; restore address of 1st level page table
ldr r0, =PHYBASE
add r10, r10, #(0x3000 / 4) ; (r10) = ptr to 1st PTE for 0x30000000 (30000000>>18)得到物理地址30000000的页表距离页表表头的偏移地址
add r0, r0, #0x1E ; 1MB cachable bufferable,cachable属性
orr r0, r0, #0x400 ; set kernel r/w permission 读写属性
mov r1, #0
mov r3, #64 ;要映射64MB的地址,r3指定未创建页表数目
45 mov r2, r1 ; (r2) = virtual address to map Bank at
cmp r2, #0x20000000:SHR:BANK_SHIFT ;0x20000000>>20=512MB
add r2, r10, r2, LSL #BANK_SHIFT-18 填充页表,直到操作到512MB字节
strlo r0, [r2]
add r0, r0, #0x00100000 ; (r0) = PTE for next physical page
subs r3, r3, #1
add r1, r1, #1
bgt %b45
ldr r10, =PTs ; (r10) = restore address of 1st level page table
; The page tables and exception vectors are setup.
; Initialize the MMU and turn it on.
mov r1, #1
mcr p15, 0, r1, c3, c0, 0 ; setup access to domain 0
mcr p15, 0, r10, c2, c0, 0
mcr p15, 0, r0, c8, c7, 0 ; flush I+D TLBs,清除指令和数据cache,还有TLB
mov r1, #0x0071 ; Enable: MMU MMU标志,用于写入到CPU的协处理器中启用虚拟内存
orr r1, r1, #0x0004 ; Enable the cache cache标志,同上
ldr r0, =VirtualStart ;r0中存储启用虚拟内存后要使用的虚拟地址
cmp r0, #0 ; make sure no stall on "mov pc,r0" below
mcr p15, 0, r1, c1, c0, 0 设置MMU标志,启用虚拟内存
mov pc, r0 ; & jump to new virtual address ;跳转到VisualStart的虚拟地址,此后程序中所有的操作均使用虚拟地址
nop
VirtualStart
mov sp, #0x8C000000
add sp, sp, #0x30000 ; 设置栈空间,0x8C030000是栈顶,所对应的物理地址是0x30030000,从boot.bib中关于栈空间定义的语句(STACK 8c02c000 00004000 RESERVED)可以看出
b main ;跳转到BootLoader的main函数,StartUp结束
ENTRY_END
LTORG