驱动模型:
1. .LIB库文件,与操作系统内核链接,随操作系统启动一起加载,关闭一起关闭。效率高,但是缺乏灵活性和扩展性。
2. 延迟加载,但是仍在系统的内核空间中。实现了驱动的动态加载,但是容易导致系统不稳定。
3. wince 采用用户态下的DLL文件形式存在。
Wince驱动宿主
1. device.exe 设备管理器,负责加载和管理绝大多数的设备驱动:网卡、电池、声卡、串口、NLED、USB设备驱动、PCMCIA驱动等。
2. GWES.dll 某驱动仅被GWES进程使用。如鼠标、键盘、显卡和触摸屏等驱动。
3. FileSys.dll 负责管理wince中的对象存储和文件系统。如FAT文件系统驱动、CDFS文件系统驱动等。
单体驱动程序
直接与硬件交互,暴露DDI: Device Driver Interface给操作系统。应用于对效率要求较高的场合或者较简单的硬件设备驱动。
分层驱动程序
2层:上面一层,模型设备驱动(Model Device Driver,MDD);下面一层,平台相关驱动(Platform Dependence Driver,PDD)。MDD包含某一类型驱动的通用代码,通常Platform Builder会自带,无需修改。PDD包含特定硬件或平台专用的代码,供MDD调用。当移植示例驱动时,只需对PDD层代码进行修改。
另外,MDD和PDD之间还需要一个接口协议。故在分层驱动中,有2类接口:操作系统和MDD之间的DDI,MDD和PDD之间的DDSI(Device Driver Service provider Interface)。
MDD:
ü 包含某一类驱动所通用的代码,对于同一类型驱动,代码可重用。通常无需改动
ü 调用PDD层访问硬件设备
ü 与PDD层代码链接,定义PDD层必须实现的DDSI函数,并在代码中使用
ü 对于操作系统,实现DDI函数,供操作系统和驱动程序交互
ü 进行中断处理,中断处理线程IST通常位于这一层
ü 编译后生成的Lib库可与不同的PDD库进行链接
PDD:
ü 包含与特定硬件相关的代码,对不同的硬件或标准,有不同的实现
ü 只能与某一类MDD协同工作
ü 实现MDD所需的DDSI函数
与平台无关的驱动sample位于:%WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS
与平台相关的位于:%WINCEROOT%/PLATFORM/<BSP Name> /SRC/DRIVERS
设备管理器
² 为大量DLL形式的驱动程序提供宿主进程,也就是把驱动程序的DLL文件加载到Device.exe进程的地址空间内。
² 在系统启动和新设备连接时负责加载和初始化驱动程序;在设备不使用时卸载。
² 对驱动程序进行统一管理,并可在特殊驱动事件发生时对其他应用程序发出通知。
² 实现并暴露设备管理相关的API函数,供其他应用程序调用,进而对设备进行访问和控制。
² 提供电源管理的接口。
² 负责对I/O资源进行管理。
² Device.exe会调用其他DLL来共同完成设备管理的功能。如:Devmgr.dll会进行大部分的设备管理工作,BusEnum.dll会完成驱动加载和总线枚举的工作等。
在wince中,设备的初始化可分成2个阶段:device.exe本身的初始化,外设的枚举和加载。
Device.exe本身的初始化
1. wince冷启动后,操作系统内核NK首先运行,然后NK根据注册表HKLM/init下的内容陆续启动一些其他进程,device.exe就是在此时被启动。
2. 当device.exe启动后,进行一系列自身的初始化工作:先初始化本身的一些数据结构,再初始化I/O资源管理器和电源管理器。
3. device.exe根据注册表的设置,加载BusEnum.dll,让BusEnum.dll负责加载和初始化所有的内置外设。
至此,控制权交给busenum.dll,device.exe本身的初始化结束。
外设的枚举和加载
Busenum.dll的源代码:%WiNCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/BUSENUM
1. 当设备自身的初始化结束后,device.exe会在注册表HKLM/Drivers下读取RootKey的值,该值保存总线枚举器的位置,一般为“Drivers/BuiltIn”。
2. 根据RootKey的值,device.exe加载总线枚举器。如HKLM/Drivers/BuiltIn。
设备管理器直接调用ActivateDeviceEx函数来加载busenum.dll,然后调用后者的导出函数Init()。
枚举过程会遍历HKLM/Drivers/BuiltIn下所有子键。ActivateDeviceEx函数根据里面注册表的信息,把对应的驱动DLL加载到device.exe的地址空间内,然后在注册表HKLM/Drivers/Actice键下增加一个子键,来记录已经加载的驱动程序。最后调用设备的Init函数。如果设备时即插即用设备,则还有负责发送即插即用的通知。
3. busenum.dll的init函数会遍历HKLM/Drivers/BuiltIn下的所有子键。每个子键都对应着一个预先配置的驱动程序信息。busenum.dll读取这些信息,然后加载驱动程序。加载方式调用ActivateDeviceEx函数来完成的。
如果碰到总线驱动程序,会引起递归,该总线驱动要负责枚举查在该总线上的所有设备,并加载对应的驱动程序。
此外,应用程序也可显式的调用ActivateDeviceEx函数来加载一个驱动程序。
设备文件的名称(Device File Names)
Applications can access peripheral devices through file system functions, such as CreateFile. The file I/O operations are redirected to the stream interface.
The following table shows descriptions and examples for the three device namespaces.
Namespace description
Example
Three letter prefix, followed by a number between 0 and 9, followed by a colon.
"LPT2:"
$bus mount point, followed by the bus name, bus number, device number, and function number.
"/$bus/PCMCIA_0_0_0"
$device mount point, followed by a three letter prefix representing the device, followed by a number.
"/$device/COM7"
应用程序的通知
HANDLE RequestDeviceNotifications( const GUID* devclass, HANDLE hMsgQ, BOOL fAll);BOOL StopDeviceNotifications( HANDLE h);
资源的管理
注册表HKLM/Drivers/Resources/IRQ和IO项记录了资源的状态。
当总线驱动程序希望申请一定的资源时,可以通过ResourceRequest()和ResourceRequestEx()函数来申请,后者可申请独占的资源。可通过ResourceRelease()释放。
中断处理
Wince下的中断处理分为2个阶段:处于内核模式的中断服务例程ISR(Routine)和处于用户模式的中断服务线程IST(Thread)。
ISR和IRQ(Interrupt ReQuest)是1对多的关系。ISR负责把IRQ转化程逻辑中断并返回给内核;IST则负责中断的逻辑处理。
If the kernel's exception-trapping code receives a hardware interrupt, and then the kernel detects an exception, the kernel handles the hardware interrupt.
Otherwise, go to the next step.
The kernel's interrupt support handler notifies the ISR to disable the specific interrupt until the required handling completes.
All other interrupts remain enabled.
The exception handler calls the ISR to determine how to handle the interrupt.
Note:
Details might vary depending on the CPU architecture.
The kernel receives the return value from the ISR, which lets the kernel know what to do about the interrupt.
The following table shows the possible responses from the kernel.
Response
Description
SYSINTR_NOP
The kernel does nothing.
SYSINTR_RESCHED
The kernel reschedules the IST. The interrupt was for the OS timer tick thread.
SYSINTR_XXX, logical interrupt value for that specific interrupt source.
The kernel triggers the interrupt sources ISR so the IST wakes up and does its work. Then, the IST creates an event and waits on it.
When the IST wakes up, the IST does whatever work it needs to do to handle the interrupt.
This could mean moving data into a buffer or interpreting the data in some meaningful way.
As needed, the IST calls various I/O routines to access the hardware to do its work.
When the IST finishes its work, it notifies the kernel by calling InterruptDone.
The kernel calls the OAL function OEMInterruptDone to complete all handling for the interrupt.
The OAL notifies the hardware to re-enable the interrupt.
导致ISR延迟(从发生中断到ISR首次执行之间的时间)的因素包括:
Ø 中断被关闭的时间
Ø 总线指令锁定处理器的时间(检查中断并锁定)
Ø 内核ISR的执行时间+导向ISR的时间
导致IST延迟(从中断发生到执行IST中第一行代码之间的时间)的因素包括:
Ø ISR延迟时间
Ø ISR执行时间
Ø OS执行系统调用的时间
Ø 调度IST的时间
访问物理内存
Wince提供了相对简单的物理内存呢访问方式。
物理内存结构体PHYSICAL_ADDRESS(定义在ceddkh中,用64位来代表物理地址,对大多数32位的cpu,只需吧HighPart设置为0即可)。
VirtualAlloc()负责在虚拟内存空间内保留一段虚拟内存,而VirtualCopy()负责吧一段物理内存和虚拟内存绑定。因此,最终对物理内存的访问还是通过虚拟地址进行的。
CEDDK还提供MmMapIoSpace()函数来把一段物理内存直接映射到虚拟内存。用该函数申请的内存要有那个MmUnmapIoSpace()释放。函数的源代码:%WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/CEDDK/DDK_MAP
此外,wince还提供了AllocPhysMem()和FreePhysMen()函数,用来申请和释放一段连续的物理内存。函数可保证申请的物理内存是连续的,这对DMA设备尤为重要。
DMA处理
Using CEDDK.dll functions
Using kernel functions
CEDDK.dll provides these functions for obtaining a buffer for DMA transfers:
HalAllocateCommonBuffer
HalFreeCommonBuffer
HalTranslateSystemAddress
The kernel provides these functions for obtaining a buffer for DMA transfers:
AllocPhysMem
FreePhysMem
CEDDK.dll functions can handle bus and hardware platform-specific address translations.
You must handle hardware platform-specific address translations. You can call HalTranslateSystemAddress to translate the address.
CEDDK.dll functions are useful for common buffer DMA.
The kernel functions may be useful for scatter/gather DMA.
CEDDK.dll functions use a default memory alignment of 64 KB.
The kernel functions allow you to change the default memory alignment.
电源管理
CEDDK库
CEDDK.dll exposes functions typically used by drivers for handling bus address translations, allocating and mapping device memory, setting up direct memory access (DMA) buffers, performing I/O, and so on. You can implement CEDDK.dll to support any bus on your hardware platform.
While you are completing your OAL, implement CEDDK.dll. To implement CEDDK.dll, you can use the default files or you can create your own to support your hardware platform. Many platforms only implement a unique Ddk_bus.c file and link to the other libraries built from the public tree.
The following table shows the location of the CEDDK.dll source code in the %_WINCEROOT%/Public/Common/OAK/Drivers/CEDDK directory.
Library
Location
Address mapping abstraction 地址映射
DDK_MAP
Bus abstraction 总线访问
DDK_BUS
DMA abstraction DMA操作
DDK_DMA
I/O abstraction I/O操作
DDK_IO
Power management abstraction 电源管理
DDK_POWER
Time-based abstraction
DDK_TIME
Registry Helper
The registry helper functions and structures allow you to read resource configurations from the registry
registry helper functions.
Function
Description
DDKReg_GetIsrInfo
This function populates a DDKISRINFO structure with the ISR information from the registry. If an ISR DLL is specified, a handler entry point and an IRQ must also be specified.
DDKReg_GetPciInfo
This function populates the DDKPCIINFO structure with the standard PCI device instance information from the registry.
DDKReg_GetWindowInfo
This function fills the DDKWINDOWINFO structure with the address window information it reads from the registry. This information provides everything a driver must have to map its memory windows.
流式接口驱动
1. 加载驱动程序,读取注册表
2. 获取驱动程序的DLL文件名
3. 设备管理器调用LoadDriver函数,把xxx.dll加载到自己的虚拟地址空间内
4. 在HKLM/Drivers/Active下记录加载信息
5. 设备管理器调用驱动程序中的XXX_Init函数,并把上一步中添加的注册表项的完整路径作为XXX_Init的第一个参数传入驱动程序内
6. XXX_Init中,通常需要对硬件进行一些最基本的初始化操作,例如打开硬件设备、映射硬件的I/O和内存等
前6步,流式驱动程序已经被成功加载,下面是对驱动程序的操作。
7. 应用程序要打开设备时,首先调用CreateFile打开设备。
8. 设备管理器调用驱动程序中XXX_Open()函数打开设备,驱动程序可能会对硬件进行一些额外的初始化工作,使硬件进入工作状态。
9. XXX_Open()把打开设备的结果返回给设备管理器。
10. 设备管理器把XXX_Open()的结果返回给应用程序中的CreatFile()函数调用。
通过7-10步,设备已被成功打开,接下来可对设备进行读/写和控制操作。以从设备中读取数据为例,进一步说明流式驱动的工作原理。
11. 应用程序使用CreateFile()返回的句柄作为ReadFile()的第1个参数,向设备发送读请求。同样ReadFile()也要经过filesys.exe转发给设备管理器。
12. 设备管理器调用驱动程序中的XXX_Read()读取信息。
13. 驱动程序中的XXX_Read()与硬件交互,读取必要的信息,然后返回给设备管理器,再返回给应用程序。
应用程序可调用CloseHandle()关闭设备。
当系统不再使用该设备时,应用程序可调用DeactivateDevice()把驱动程序卸载。此时,设备管理器负责把XXX.dll从device.exe的虚拟地址空间中移除,并在HKLM/Drivers/Active下,移除记录。
实现流式接口驱动:
1. 为驱动选择一个前缀
2. 实现流式接口驱动DLL所必需的接口函数
3. 编写DLL的导出函数定义文件.DEF
4. 为驱动程序配置注册表
BSP
BSP一般包含
Ø Boot Loader:引导程序是在硬件开发板上执行的一段代码,主要功能时初始化硬件,加载操作系统映像(Run-time Image)到内存,然后跳转到操作系统代码去执行。
Ø OEM Abstraction Layer:是操作系统内核抽象处理的与硬件交互的接口,其实现代码通常是与硬件高度相关的。当引导程序引导操作系统结束后,由OAL负责硬件平台的初始化、中断服务例程ISR、实时钟(Real Time Clock,RTC)、计时器(Timer)、内核调试、开关中断和内核性能检测等工作。OAL的代码经编译链接,成为内核的一部分。
Ø Device Driver:
Ø Configuration File:是一些包含配置信息的文本。这些配置信息通常与操作系统映像或源代码有关。例如:告诉编译系统如何编译某些源代码,或高数编译系统如何配置最终的操作系统映像文件。BSP的配置文件包括:BIB, DB, REG和DAT四类平台初始化文件,这些文件告诉Make Image工具如何生成操作系统映像;配置文件还应包括Sources和DIRS文件;还有一个CEC文件。
开发BSP步骤
1. 硬件准备:硬件开发和测试。对BSP开发人员,需掌握:对应CPU的汇编语言和微软编译器的语法、对应CPU的功能、开发板的datasheet、硬件调试的技巧。
2. clone参考BSP
3. 开发Boot Loader:期望结果是通过传输介质(以太网口、串口等)把一个测试的.BIN文件加载到内存中。
4. 开发OAL
5. 添加驱动程序
6. 增加电源管理
7. 发布BSP
Boot Loader构成
ü BLCommon
ü OEM代码
ü EBoot
ü 存储管理
ü EDBG驱动程序
其中OEM代码需要自己编写,其他由wince提供。Wince提供源代码和LIB库文件。
模块名
介绍
可否修改
源代码位置
BLCommon
实现通用的Boot Loader框架
否
%WINCEROOT%/PUBLIC/COMMON/OAK/
DRIVERS/ETHDBG/BLCOMMON
OEM代码
需要用户实现的代码
必须
无
EBoot
以太网操作相关的函数
否
%WINCEROOT%/PUBLIC/COMMON/OAK/
DRIVERS/ETHDBG/EBOOT
存储管理
存储分区管理和Flash驱动
可能
%WINCEROOT%/PUBLIC/COMMON/OAK/
DRIVERS/ETHDBG/BOOTPART
EDBG驱动程序
EBOOT使用的驱动程序
可能
%WINCEROOT%/PUBLIC/COMMON/OAK/
DRIVERS/ETHDBG
BLCommon功能:把Boot Loader搬到RAM里以获得更快的访问速度;解析以及接报.BIN文件的格式;检验校验以及跟踪加载进度;调用OEM代码处理硬件初始化等操作。可直接链接Blcommon.lib来使用。
EBoot库:实现了DHCP(Dynamic Host Configuration Protocol),TFTP(Trivial File Transfer Protocol)和UDP(User Datagram Protocol)网络服务。可直接连接Eboot.lib使用。
OEM代码是以OEM开头的一些函数,需要根据自己的硬件特点和需求实现。
此外,Boot Loader中有2类设备经常要用到:1)以太网卡,用来下载操作系统映像;2)Flash,用来存储Boot Loader和下载的操作系统映像。因此Boot Loader对这两类硬件设备也抽象出了统一接口,也被称为驱动程序,分别叫做EthDbg和FMD驱动。与wince中使用的驱动接口是不同的,不能通用。
Boot Loader的工作流程
1. Startup()调用Main();
2. 调用BootLoaderMain()
3. KernelRelocate
4. OEMDebugInit
5. OEMPlatformInit
6. OEMPreDownload
7. DownloadImage
8. OEMLaunch
系统初始化函数
Wince的Boot Loader中,系统商店执行的第1个Eboot函数通常都叫做Startup()——系统初始化函数,一般用汇编语言编写,位于Startup.s文件中。Startup()函数负责初始化CPU和一些核心的逻辑部件,大致完成如下工作:
ü 把CPU设置为合适的运行状态。此状态可以无限制的访问内存和硬件。在X86下时Ring 0态;而在其他体系结构下通常称为特权态(Supervisor Mode)。
ü 在CPU级别关闭所有中断
ü 确保MMU和TLB都已经关闭
ü 使Cache和Write Buffer失效
ü 初始化内存控制器
ü 初始化其他的片上设备,如时钟。只需做最基本的初始化工作,其他可稍后进行
ü 设置栈指针,后面的C语言执行环境要用到
ü 设置并打开MMU,进行物理和逻辑地址映射,并打开Cache
ü 把EBoot的代码复制到RAM中,然后跳转到RAM的EBoot代码
ü 跳转到C语言的Main()函数
正常启动时,Startup()函数做最基本的平台初始化,然后就跳转到C语言的Main()函数。如果是休眠唤醒,则需要做其他特殊处理。
Note:
l 在整个Boot Loader中,Startup()是唯一必须使用汇编语言编写的函数。
l 在整个Boot Loader执行过程中,中断一直处于关闭状态,因此所有对外设的访问都是通过轮询的方式进行的。
l MMU在整个过程中基本都是开启的,因此在Boot Loader中访问内存都是通过虚拟内存访问的。
Boot Loader的主控函数- BootloaderMain
Startup()跳转到Main()后,系统转入C语言代码执行环境。Main()需要OEM实现,通常位于Main.c文件中,其最主要的任务是调用BLCommon中的BootloaderMain()。这样,整个Boot Loader的控制权就交给了BootloaderMain()函数。
BootloaderMain()是Boot Loader的主控函数,BLCommon库中最重要的函数,它控制整个Boot Loader的完整执行过程。源代码:%WINCEROOT%/PUBLIC/COMMON/OAK
/DRIVERS/ETHDBG/BLCOMMON/Blcommon.c文件中。
void BootloaderMain (void)
{
// 把全局变量定位到RAM
if (!KernelRelocate (pTOC))
{
// spin forever
HALT (BLERR_KERNELRELOCATE);
}
// (1) 初始化调试端口,然后就可以使用OEMWriteDebugString()函数了
if (!OEMDebugInit ())
// (3) 初始化平台(clock, drivers, transports, etc)
if (!OEMPlatformInit ())
// system ready, preparing for download
EdbgOutputDebugString ("System ready!/r/nPreparing for download.../r/n");
// (4) 调用OEM编写的OEMPreDownload 函数决定是否需要下载映像
switch (dwAction = OEMPreDownload ())
{
case BL_DOWNLOAD:
// (5) 下载映像
if (!DownloadImage (&dwImageStart, &dwImageLength, &dwLaunchAddr))
bDownloaded = TRUE;
// 映像下载后,检查pTOC signature ("CECE")
if (*(LPDWORD) OEMMapMemAddr (dwImageStart,
dwImageStart + ROM_SIGNATURE_OFFSET) == ROM_SIGNATURE)
{
dwpToc = *(LPDWORD) OEMMapMemAddr (dwImageStart, dwImageStart
+ ROM_SIGNATURE_OFFSET + sizeof(ULONG));
// need to map the content again since the pointer
//is going to be in a fixup address
dwpToc = (DWORD) OEMMapMemAddr (dwImageStart, dwpToc
+ g_dwROMOffset);
}
// 继续执行
case BL_JUMP:
// Before jumping to the image, optionally check the image signature.
// NOTE: if we haven't downloaded the image by now, we assume that it'll be loaded from local storage in OEMLaunch (or it already resides in RAM from an earlier download), and in this case, the image start address might be 0. This means that the image signature routine will need to find the image in storage or in RAM to validate it. Since the OEM"s OEMLaunch function will need to do this anyways, we trust that it's within their abilities to do it here.
//
if (g_bBINDownload && g_pOEMCheckSignature)
{
if (!g_pOEMCheckSignature(dwImageStart, g_dwROMOffset,
dwLaunchAddr, bDownloaded))
HALT(BLERR_CAT_SIGNATURE);
}
// (5) final call to launch the image. never returned
OEMLaunch (dwImageStart, dwImageLength, dwLaunchAddr,
(const ROMHDR *)dwpToc);
// should never return
// fall through
default:
// ERROR! spin forever
HALT (BLERR_INVALIDCMD);
}
}
重新定位全局变量函数- KernelRelocate
在BLCommon.c文件中。将全局变量转移到RAM中。
static BOOL KernelRelocate (ROMHDR *const pTOC)
{
ULONG loop;
COPYentry *cptr;
if (pTOC == (ROMHDR *const) -1)
{
return (FALSE); // spin forever!
}
// This is where the data sections become valid... don't read globals until after this
for (loop = 0; loop < pTOC->ulCopyEntries; loop++)
{
cptr = (COPYentry *)(pTOC->ulCopyOffset + loop*sizeof(COPYentry));
if (cptr->ulCopyLen)
memcpy((LPVOID)cptr->ulDest,(LPVOID)cptr->ulSource,cptr->ulCopyLen);
if (cptr->ulCopyLen != cptr->ulDestLen)
memset((LPVOID)(cptr->ulDest+cptr->ulCopyLen),0,cptr->ulDestLen-cptr->ulCopyLen);
}
return (TRUE);
}
1. 问什么要把全局变量重定位到RAM中? Boot Loader大多数代码用C编写,在被编译成二进制代码后,这些全局变量被放在可执行文件的一个数据段中,然后被烧写到目标设备上。很多情况下,Boot Loader是在只读媒体上运行的(如NOR Flash ROM),这样要对全局变量进行写操作时就会失败。因此需要把全局变量所在的数据段转移到RAM,以确保全局变量可写。
2. 如何转移数据段? 由pTOC决定。
其定义:ROMHDR * volatile const pTOC = (ROMHDR *const) -1;
此指针描述了整个ROM几乎所有的信息。定义的时候,赋予非法值-1。编写代码时,代码不可能知道自己将会被烧写到什么样的ROM里,因此全局变量pTOC是在工具RomImage.exe在把EBOOT打包为ROM文件时初始化的。
经重定位后,EBOOT的全局变量就可以在RAM中正确的读写了,从而保证Eboot代码的正确运行。接下来可以进行端口初始化等其他任务了。
初始化调试端口- OEMDebugInit
代码由OEM实现。通常初始化一个UART端口,用来输出调试信息,这样就尅方便的在开发机上使用超级终端等接收调试信息。
初始化平台- OEMPlatformInit
代码由OEM实现,主要初始化目标板上的设备:
Ø 实时钟 EBoot在后面的网络下载及用户输入等判断超时都需要通过实时钟进行。
Ø 显示屏 可选。如果要在操作系统启动时,在显示屏上显示logo等信息,在此时初始化显示屏。
Ø Flash 如果下载后的映像必须烧写到Flash中或其他方式要用的Flash,则可在这里初始化Flash。另外,FMD驱动程序也可在这里初始化。
Ø 网卡 EBoot必须用到网卡下载映像,在这里初始化网卡,包括MAC地址、I/O方式等。
Ø BSP的共享参数 OAL和EBoot通常会共享一些参数,需要在这里初始化。
如果要在EBoot上与超级终端中的用户交互,也可在这个步骤进行。
OEMPlatformInit()结束后,就可以开始下载工作了。
下载映像之前函数- OEMPreDownload
代码有OEM实现。主要任务是完成以太网下载前的一些准备工作,包括:通过DHCP获得IP地址,初始化TFTP服务等。
下载映像函数-DownloadImage
是BLCommon库提供的一个库函数,作用是从远程开发机上下载操作系统映像。与传输介质无关,DownloadImage()调用OEMReadData()函数来读取下载的数据。
支持一次下载多个BIN文件(Muti-Bin)构成的映像。
除了下载外,DownloadImage还负责把映像的长度、大小和运行起始地址返回给调用者。
如果用户配置的映像下载地址是一段Flash的地址,那么DownloadImage函数还负责调用OEM函数,把下载的系统映像烧写到Flash中去。
源代码:%WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/ETHDBG
/BLCOMMON/mian.c
启动映像函数-OEMLaunch
由OEM实现。
Boot Loader的实现
Boot Loader由微软提供的库、EBoot驱动和OEM函数组成。
有2类OEM函数:必须实现的,可选的。前者,BLCommon库会直接调用他们,如果不实现,EBoot的链接就会失败。对后者,BLCommon库通常使用函数指针调用,初始化状态下,这些函数的指针为空,如果用户实现了,就可给这些函数指针赋值。在库调用时,库会检查函数指针的值:如果为空,不调用。
必须实现的OEM函数
1. 初始化函数:在EBoot启动时执行,主要负责初始化硬件平台。
Startup()
硬件上电后执行的第一个函数,初始化硬件平台,然后跳转到C程序的Main()入口
Main()
第一个C函数,调用BootLoaderMain(),转到BLCommon的控制流
2. 与控制流相关的函数:一般被BootLoaderMain调用,控制整个Boot Loader的执行流程。
OEMDebugInit()
BLCommon调用的第一个OEM函数,负责初始化调试串口
OEMPlatformInit()
BLCommon库调用这个函数来初始化硬件平台,外设。
OEMPreDownload()
BLCommon在下载映像前调用。功能可自定义,如获取IP地址等
OEMLaunch()
BLCommon最后调用的函数,启动操作系统映像
3. 与调试相关的函数:输出一些信息,以便跟踪代码的执行,与用户交互
OEMInitDebugSerial()
初始化串口。通常由OEMDebugInit调用
OEMReadDebugByte()
从调试端口读取一个Byte的数据
OEMWriteDebugByte()
往调试端口写入一个Byte数据
OEMWriteDebugString()
往调试断开写入一个字符串
4. 与下载相关的函数:
OEMMapMenAddr()
如果映像要烧到Flash中,那么可以使用此函数提供一个临时的RAM缓冲区存放下载的映像,以便下载完成后一并写入Flash
OEMReadData()
BLCommon中的DownloadImage()调用此函数,从下载的端口下载操作系统映像
OEMShowProgress()
当下载正在进行时,BLCommon调用此函数。
5. 与时钟相关的函数:OEMEthGetSecs(),通过系统时钟读取返回某个时间过去的秒数。
6. 以太网操作函数:直接调用底层的EtherDbg驱动程序。
OEMEthGetFrame()
从以太网读取数据
OEMEthSendFrame()
发送数据
7. Flash操作函数:如果Boot Loader要实现把映像烧写到Flash中的功能,则需实现这些函数。
OEMIsFlashAddr()
判断一个地址是否是Flash的地址
OEMWriteFlash()
OEMStartEraseFlash()
初始化Flash擦除操作
OEMContinueEraseFlash()
在下载映像过程中,BLCommon调用此函数来继续擦除Flash上的内容
OEMFinishEraseFlash()
当BLCommon准备开始烧写映像时调用,此时,所有的Flash擦除工作应该已经结束
可选实现的OEM函数
OEMCheckSignature()
检查BIN文件的签名
OEMMutiBINNotify()
当BLCommon要下载某个BIN文件时,会回调此函数告诉用户
OEMVerifyMemory()
检查某一段内存地址是否为合法地址
在BLCommon.h声明,在BLcommon.c中定义了3个可选函数的全局指针:g_pOEMVerifyMemory, g_pOEMCheckSignature, g_pOEMMutiBINNotify。如果OEM提供这些函数,需要提前初始化这几个全局函数指针,较好的时机是在OEMDebugInit()函数中。
编写OAL
Wince 内核NK.exe构成
模块名
介绍
可否修改
源代码位置
NK库
与CPU相关的操作系统内核代码
否
OEM代码
必须
KITL
内核无关的传输层
否
C函数库
部分C语言函数
否
PQOAL
微软提供的可重用的OAL代码
否
%WINCEROOT%/PLATFORM/COMMON/SRC
%WINCEROOT%/PUBLIC/COMMON/OAK/CSP
NK库 通常指NK.lib和NKProf.lib文件。前者含有内核基本功能,后者含有性能监控代码。根据CPU体系不同,NK库有4种:X86, ARM, MIPS, SH4类。内核代码与CPU相关,与硬件平台无关。
OAL库 由OEM实现,与硬件高度相关,通常称为OAL.lib。
KITL(Kernel Independent Transport Layer)在wince 4.0以后被引入。其基本思想是:提供操作系统内核在开发机和目标设备之间建立连接和通信的途径。KITL的特点是与具体的数据传输介质无关,数据可通过以太网、串口、USB等传输,但是对操作系统内核而言,它看到的是统一的接口。
连接建立后,内核的很多服务都可以架构在KITL上,例如内核调试和控制台等。在内核中,KITL的功能是可选的。
C函数库 由微软提供的。这部分代码是在内核中使用的C语言函数,名字为FullLibc.lib。实现OAL时,所有调用的C函数都必须在FullLibc.lib中,否则会导致找不到实现而链接失败。
Production-Quality OAL库 由微软提供。这些代码库包含对待定CPU或CPU内置的外设控制器的操作代码。在实现BSP时,如果要对CPU或其内置控制器进行操作,应尽量使用PQOAL库而不要自己实现。在BSP代码中,基本上以OAL开头的函数都是PQOAL库中的函数。
OAL的启动流程与原理
ARM平台下,从系统上电,OAL代码开始执行到操作系统开始进程调度的过程大致经过了:Startup(), KernelStart(), ARMInit(), KernelInit()和FirstSchedule()函数。
系统初始化-Startup()
有两种可能性:一是系统中不存在Boot Loader,开机直接执行的就是OAL代码;另一种是系统中存在Boot Loader,OAL代码经过Boot Loader引导后才开始执行。
如果OAL不是经过Boot Loader引导的,那么系统上电后立即执行的就是OAL的Startup函数,此时,Startup函数需要对硬件进行基本的初始化,大致的初始化内容与Boot Loader中的Startup函数一致。
如果OAL是经过Boot Loader引导而执行的,那目标设备上的许多硬件设备在Boot Loader中都已经被初始化过了,无需再进行重复工作,仅需完成跳转到OAL的主控函数KernelStart开始执行。
汇编。
OAL的主控函数-KernelStart()
负责整个系统启动的控制流,由微软提供,属于内核代码的一部分。涉及到CPU相关的操作,所以整个代码也是使用汇编语言编写的。
主要完成如下工作:
1. 初始化页表、打开MMU和Cache
KernelStart做的第一件事就是打开处理器的MMU,进入虚拟内存模式,为此需要首先设置页表。
页表是虚拟地址和物理地址的映射表,MMU利用查找页表把虚拟地址映射到物理地址。页表除了记录虚拟地址的映射信息外,还包含此段内存的访问权限,这样就可以实现内存的保护机制。
Wince的做法是:用户设置一个映射的数组,wince读取此数组的代码去设置相应的页表。数组的声明是:OAL_ADDRESS_TABLE(Oal_memory.h),有3项内容:CA(虚拟地址),PA(物理资质), size(大小,以MB为单位)。此数组由OEM填写,在oemaddrtab_cfg.inc内。表的合法虚拟地址是0x8000 0000~0x9FFF FFFF的512MB,因为wince最多支持512MB的物理内存。但是KernelStart会根据这个表建立2分虚实映射。0x8000 0000~0x9FFF FFFF带缓存,0xA000 0000~0xBFFF FFFF不带缓存的。
KernelStart读取此数组的内容,然后根据数组内容设置相应的页表,并打开MMU。
2. 设置异常向量跳转表
中断和异常都是异步发生的事件。当异常发生时,系统将停止目前正在执行的代码转而执行事件相应的服务程序,其入口点就是异常跳转表。跳转表的长度是8字节,每个字节对应一种异常发生后要执行的指令,分别为::重启、未定义指令、软件中断、取指令异常、数据访问异常、保留、IRQ和FIQ
3. 初始化栈
当MMU和Cache被打开后,还须初始化每个模式下的栈,才能开始执行C语言代码 。
在ARM中,第一个由C编写的控制流函数是ARMInit()。
初始化ARM平台- ARMInit()
由微软提供,源代码公开。主要功能是初始化基于ARM的硬件平台(包括Cache、中断、时钟以及内核传输层等)。主要工作如下:
1) 调用KernelRelocate()函数进行重定位。此过程与Boot Loader的类似。
2) 调用OEMInitDebugSerial()函数初始化调试输出使用的串口,同Boot Loader。
3) 调用OEMInit()函数来初始化目标设备上的硬件。
4) 调用KernelFindMemory()来获取所有的物理内存信息并把内存分成应用内存和对象存储2部分。
初始化内核-KernelInit()
首先,初始化系统API函数调用;然后依次调用HeapInit(), InitMemoryPool(), ProcInit(), SchedInit()初始化系统堆、内存池、第1个进程和线程。
开始调度-FirstSchedule()
OAL的实现
OAL作为操作系统内核的一部分,在整个操作系统运行的过程中时刻运行,因此OAL比Boot Loader更加难于编写和测试,而且OAL代码编写的好坏不但会直接影响操作系统的稳定性、安全性和性能,还会直接影响驱动程序和应用程序的功能。
中断处理
OEMInterruptEnable()
打开某个硬件中断
OEMInterruptDisable()
关闭某个硬件中断
OEMInterruptDone()
某个硬件中断处理完后调用
OEMInterruptHandler()
ARM平台:IRQ的中断处理例程
OEMInterruptHandlerFIQ()
ARM平台:FIQ的中断处理例程
时钟操作
与PWM Timer不同,实时钟通常在系统关机后仍然可由备用电池来维持,因此可用来唤醒系统。
OEMGetRealTime()
OEMSetRealTime()
OEMSetAlarmTime()
Cache操作
CPU状态管理
OEMIoControl
其他OAL功能
如KITL、进行内核模块的数字签名验证、保存注册表等。
Production Quality OAL
从wince5.0开始引入,目的是尽可能把系统的功能模块化,同时规范OEM代码的目录结构和文件命名方式,从而达到简化开发的目的。
PQOAL包含以下几个要素:一组与特定CPU相关的通用库;按功能明确uaf的OAL软件模块;标准的目录结构和文件命名方式。
Platform/<BSP名称>/下的目录结构
子目录
描述
Cesysgen
包含一个makefile,它调用cefilter过滤所有files目录下的平台初始化文件
Files
包含与特定BSP相关的平台初始化文件
Src
所有BSP的源代码目录,包含Boot Loader,OAL以及一些头文件
Src/Bootloader
Boot Loader的源代码,可能包含Ethernet Boot Loader或Serial Boot Loader的子目录
Src/Bootloader/Eboot
Eboot的源代码
Src/Common
包含Boot Loader和OAL通用的代码
Src/Drivers
包含BSP中所有外设的驱动程序代码
Src/Inc
BSP头文件
Src/Kernel
包含与设备相关的OAL代码,用来编译和链接OAL
Src/Kernel/Kern
包含构建脚本和可选的函数桩,用来构建最基本的wince内核
Src/Kernel/Kernkitl
包含构建脚本和可选的函数桩,用来构建支持KITL的wince内核
Src/Kernel/Kernkitlprof
包含构建脚本和可选的函数桩,用来构建支持KITL和内核性能检测(Profile)的wince内核
Src/Kernel/OAL
包含与硬件平台相关的OAL代码
Platform/<BSP名称>/Src/Inc目录下的文件命名
文件名
描述
Args.h
包含启动参数的结构体定义,还有在Boot Loader与OAL之间传递的参数列表
Boot.h
Boot_cfg.h
Bsp.h
包含所有其他的头文件,这样在代码中只要包含Bsp.h即可
Bsp_base_reg_cfg.h
包含板级的寄存器地址的定义
Bsp_cfg.h
包含与BSP相关的配置信息,如串口通信速率等
Image_cfg.h
包含与映像相关的内存映射信息,如映像的起始地址等
Ioctl_cfg.h
Ioctl_tab.h
包含OEM IoControl的控制吗与对应处理函数的表
Kitl_cfg.h
Oemaddrtab_cfg.inc
包含虚拟地址到物理地址的映射表
Platform/<BSP名称>/Src/Kernel/OAL目录下的文件命名
文件名
描述
Args.c
包含对特定启动参数的处理。参数可能是Boot Loader的传递,或从本地存储中读取
Debug.c
包含调试输出相关的代码,如串口输出
Init.c
包含需要OEM实现的硬件平台的初始化代码,如OEMInit()
Intr.c
包含板级中断处理的相关代码
Ioctl.c
包含本地硬件的IOCTL实现
Kitl.c
包含KITL使用的硬件平台初始化代码