到现在为止,我们已经了解了S3C2410平台上WinCE6.0的启动过程,包括NBOOT、EBOOT、OAL.exe、Kernel.dll的工作流程。关于WINCE600的目录也做了介绍。准备工作都做好了,接下来开始介绍S3C2410平台上WinCE6.0的移植。BSP的移植很大一部分是代码的移植,所以,这里仍然以代码为主线,以函数为单位来做介绍。BSP的代码一般来说也不是自己从无到有CODE出来的,大多由芯片厂商或者微软提供,OEM厂商需要做得事情是修改相关代码以满足自己硬件的特定需求,快速推出新的产品。如果BSP的移植从零开始,那么市场的先机就完全丧失了。所以这里的介绍将基于现有代码,着重说明移植时需要修改和注意的地方。
前面已经提到,EBOOT加载完NK后,执行的第一个函数为OAL.exe中的Startup函数,接下来我们就开始分析这个函数的实现,它的代码如下:
1 INCLUDE kxarm.h
2
3 IMPORT KernelStart
4
5 TEXTAREA
6
7 ;Include memory configuration file with g_oalAddressTable
8
9 INCLUDE oemaddrtab_cfg.inc
10
11 LEAF_ENTRY StartUp
12
13 ; Compute the OEMAddressTable's physical address and
14 ; load it into r0. KernelStart expects r0 to contain
15 ; the physical address of this table. The MMU isn't
16 ; turned on until well into KernelStart.
17
18 add r0, pc, #g_oalAddressTable - (. + 8)
19 bl KernelStart
20
21 ENTRY_END
22
23 END
代码简短,功能明确,完成一个函数调用。之所以能这么简短,是因为我们采用了BOOTLOADER,有关硬件初始化的工作都在BOOTLOADER中完成了,否则这里需要做硬件初始化,DeviceEmulator的对应代码就是如此,读者可以自行查看。
代码虽然简短,但这里涉及很多内容,下面将一一分解。
第一行代码,包括了头文件kxarm.h,它主要做了符号的宏定义,以便让我们的代码更简单,如宏定义了TEXTAREA、LEAF_ENTRY。
第二行代码,引入一个外部函数KernelStart,它的实现在NKLDR中,前文已经做过详细介绍,这里不再赘述。
第三行代码,TEXTAREA是一个宏定义,标明以下为代码段。
第四行代码,包括了一个头文件,该文件中定义了一个全局变量g_oalAddressTable,它建立了虚拟内存到物理内存的映射关系。有关OEMAddressTable的内容下文会详细说明。
第五行代码,LEAF_ENTRY StartUp,其中LEAF_ENTRY也是一个宏定义,它似乎是定义了一个入口函数,并将其EXPORT。这里我们只要知道EBOOT最后跳转到NK中,就是跳到这就可以了。
第六行代码,add r0, pc, #g_oalAddressTable - (. + 8),简单来说就是将OEMAddressTable的地址放到R0中,但为什么这么写,下文再做分析。
第七行代码,bl KernelStart,调用函数KernelStart,开始启动kernel,前文以做介绍,也不再赘述。
有点啰嗦了,但对于新人来说,还是有必要说明一下。这段代码中,我们需要注意的地方有两处,一是OEMAddressTable的作用,二是第六行代码为什么这么写。
先说说OEMAddressTable。一般情况下,在WinCE中我们使用的都是虚拟内存地址,甚至在访问IO时都是如此。那么在访问虚拟内存地址时如何控制其对应的硬件或物理内存呢?这个工作由MMU(内存管理单元)来完成,MMU的功能之一就是将虚拟内存地址转换为物理内存地址。当然这种转换得有一定的逻辑关系。对于X86和ARM的CPU来说,OEMAddressTable即定义了虚拟内存到物理内存的映射关系,在MIPS和SH的处理器中,这种关系由CPU内部硬件控制,无须g_oalAddressTable。在WinCE中,这种通过OEMAddressTable的映射称为静态映射,对应的,我们还可以动态映射虚拟地址,一般用到VirtualAlloc()、VirtualCopy()、VirtualFree()这三个函数,而象MmMapIoSpace()等函数是CEDDK对前面几个函数的封装。
再看第六行代码,add r0, pc, #g_oalAddressTable - (. + 8),为什么这么写,这个+8是什么意思?反汇编看了下它对应的代码,如下图所示:
反汇编代码中的ADR是一个伪指令,它将一个地址load到R0中。源代码中采用了那么一行复杂的代码,不光S3C2410的这段代码如此,PXA270的也一样。要解释这个问题,就不得不说一下ARM中的指令预取。ARM处理器是流水线结构的,允许指令预取。在CPU执行当前指令的同时,可以从存储器中预取指令,所以当用户读取PC时,PC指向的是正在取指的指令,而非当前执行的指令,在ARM中,一般是当前执行指令下面的第2条指令(+8个字节)。所以这里的+8就不难理解了。
本文分析了OAL的启动代码,重点介绍了OEMAddressTable的作用和指令预取对代码的影响。在移植BSP的过程中,如果硬件设备无法正常访问,首先应该确认OEMAddressTable是否建立了正确的映射关系。启动代码如果不正确,将会影响WinCE的启动,这时KITL、串口打印等常规调试方法都还无法使用,所以一定要多加小心,确保顺利通过。实在不行,只能通过点灯来Debug了。这里需要注意,OAL的启动代码执行之前,应该关闭MMU,Disable MMU的工作在EBOOT中完成。