📄 wincemap.txt
字号:
OAL中的Startup的起始位置定义如下:
LEAF_ENTRY Startup
…
主要就是初始化CPU,对于ARM来说,大致步骤如下:
1. 设置CPU为supervisor模式
2. 禁用IRQ和FIQ中断
3. 关闭MMU和指令数据Cache
4. 刷新Cache和TLB,清空write buffer
5. 确认启动原因,可能是EBOOT跳转过来启动,冷启动,热启动,Watchdog复位等
6. 配置GPIO来点亮LED
7. 配置Memory控制器及时序
8. 配置中断控制器,清除所有中断
9. 初始化RTC
10. 配置CPU的电源管理
11. 设置CPU的时钟及相关的外设的时钟
12. 将OEMAddressTable的起始地址写入r0寄存器
13. 跳转到Kernelstart中
程序最后会跳转到KernelStart中,在Private目录下的armstart.s中,这里仍然是汇编,大致步骤如下:
1. 基于传入的OEMAddresstable初始化一级页表
2. 使能MMU和Cache
3. 为每一种ARM模式建立堆栈
4. 调用ARMInit函数获得kernel.dll的入口,该函数在arminit.c中定义
5. 返回kernel.dll的入口为NKStartup,在mdarm.c中定义,跳转到kernel.dll入口开始运行
6. 在NKStartup中读取CPU的ID判断ARMv6架构,初始化内核的全局变量
7. 调用OEMInitDebugSerial初始化调试串口
8. 调用OEMInit初始化相关的外设接口
9. 调用OEMCacheRangeFlush刷新Cache和TLB
10. 调用KernelFindMemory来划分对象存储空间和程序内存空间
11. 调用kernelstart函数,它与最开始的kernelstart不同,它在armtrap.s中定义
到此应该说CPU相关的初始化基本完成了,下面就是要开始WinCE内核的初始化了,大致步骤如下:
1. 跳转到armstrap.s中的kernelstart继续执行
2. 调用KernelInit函数,在nkinit.c中定义,主要完成API集的初始化,内核Heap的初始化,内存池的初始化,进程和线程的初始化,最后是映射文件的初始化
3. 在执行完KernelInit之后,会跳转到FirstSchedule进行第一次系统调度,到此内核已经运行起来了。
这里把整个的初始化过程介绍了一下,实际上Startup.s只是WinCE启动最开始的部分,完成CPU级别的初始化,这段代码都是汇编,调试起来也简单也困难,简单是因为都是顺序执行的,只是初始化CPU,困难是因为没有好的调试手段,一般在这里都是通过点LED的方式来判断程序执行的位置。
WinCE OAL中的OEMInit函数收藏
作者:ARM-WinCE
OEMInit是OAL中的重要函数,用来初始化板级的硬件设备。实际上,WinCE的内核最开始只做了CPU级的初始化,随后会初始化调试串口,然后就会调用该函数了。
应该说OAL中的OEMInit函数有点像EBOOT中的OEMPlatformInit函数,我说的是有点像,毕竟应用不同。OEMInit函数主要完成以下功能:
1. 初始化相应的硬件外设
2. 根据需要初始化KITL调试
3. 设置一些内核所需的全局变量
一般的OEMInit初始化流程如下:
1. 首先调用OALMSG(..)函数打印一条信息,这样可以知道WinCE内核已经运行到OEMInit函数中。
2. 进一步配置硬件系统,初始化简单的外设。这里可以进一步配置一些处理器的系统寄存器,比如一些外设模块的时钟,还可以点亮LED,初始化GPIO等。
3. 初始化内核的全局变量,比如设置g_pOemGlobal来初始化一些可被内核调用的函数。
4.调用OALCacheGlobalsInit来初始化一些变量保存Cache的设置参数。该函数应该在其他的Cache和TLB操作函数之前被调用。
5. 判断是否是一次干净的引导(clean boot),调用NKForceCleanBoot来迫使操作系统基于一个干净的对象存储系统(object store)来引导。
6. 调用OALIntrInit初始化中断,主要是根据需要打开一些中断,关闭不用的中断,完成一些物理中断(IRQ)到系统逻辑中断(SYSINTR)之间的静态映射。
7. 调用OALTimerInit来初始化系统时钟,为WinCE内核产生1ms的ticker。
8. 根据需要初始化显示,一般指LCD,可以在启动时显示一张图片。
9. 调用KITLIoctl函数来初始化KITL连接。
大致就这些吧,我想在OEMInit中最主要的就是中断和Timer的初始化了,他们可以让WinCE运行起来,其他的功能根据需要来实现。WinCE内核从OAL中的Startup.s开始运行,随后会调用OEMInitDebugSerial初始化调试串口,关于调试串口的实现,我以前的blog已经介绍过了。接下来就是调用OEMInit函数,该函数能够运行正常,WinCE启动就应该没太大问题了。
wince存储与地址映射技术
Windows CE采用了四层内存管理结构,从下到上依次为:物理内存,虚拟内存,逻辑内存和C/C++运行时库.其中物理内存包括:RAM(为OS和程序提供运行和缓冲空间),ROM(存储程序,包括OS和一些文件),Flash(可擦写).CE支持最大物理内存为512M.
所有进程共享4G的虚拟存储空间,它是通过以页为单位管理的,不同处理器支持页大小不同(ARM支持1K,4K,64K,1M;X86支持4K与4M).虚拟内存的申请分成保留和提交两个过程(reserve and commit).虚拟内存要求硬件上具有MMU的支持,MMU负责把虚拟地址映射到物理地址,并提供内存保护.CE把4G的虚拟内存分成两部分:低2G为用户空间,由应用程序使用;高2G为内核空间,由OS使用.
所谓逻辑内存分成堆(64K)和栈(60K).而C/C++运行时库提供了一系列内存管理函数,比如malloc,new,delete等等.
在PB的帮助中指出WINCE有两种地址:物理地址和虚拟地址.在不同架构的CPU下,概念有所区别.MIPS和SHx处理器,内核操作1G的存储(512M缓存,512M非缓存);而X86和ARM在OEMAddressTable中划分物理存储.相应的地址映射方法也分成两种:MIPS和SHx处理器,不采用MMU,直接在CPU和内核里定义;X86和ARM在OEMAddressTable中定义映射关系或者是OS启动后调用CreateStaticMapping和NKCreateStaticMapping来实现从虚拟地址到物理地址的映射.
另一种分类是映射虚拟地址的形式可以分成静态虚拟地址映射和动态虚拟地址映射.所谓静态,就是在OEMAddressTable中定义映射关系或者是OS启动后调用CreateStaticMapping和NKCreateStaticMapping来实现从虚拟地址到物理地址的映射;动态则是通过VirtualAlloc和VirtualCopy(或者调用MmmapIoSpace函数).这两种映射虚拟地址的形式区别在于静态虚拟地址只能由内核使用,用于ISR访问外设存储.而动态虚拟地址可以在应用程序里访问物理地址(比如在驱动中操作寄存器).
在X86和ARM体系的CPU里,有一个数据结构对于地址映射技术尤其重要:OEMAddressTable.这个数组定义了外设从4G的虚拟地址到512M物理地址的映射关系.它位于public\common\oak\csp\x86\oal目录下的oeminit.asm中,格式为 Virtual Address, Physical Address, Size
在X86下大小必须是4M的倍数,ARM下为1M的倍数.内核建立了两个范围的虚拟地址:从0x80000000到0x9FFFFFFF是带缓存的物理地址映射,而0xA0000000 到 0xBFFFFFFF 是不带缓存的物理地址映射.驱动访问外设时,应该用不带缓存段的虚拟地址. 要注意的一点是,如果改动了OEMAddressTable,相应要改动config.bib.
1.如果是在bootloader中访问设备寄存器,可以直接操作物理地址。
2.wince启动后,硬件上ARM和X86体系的处理器启动了MMU,操作系统只能访问到虚拟地址,不能直接操作物理内存了。但是如果是X86的CPU,由于它的外设I/O端口和存储器空间分开编址,可以直接嵌入汇编或者使用宏read_port_xxx,write_port_xxx来读写设备寄存器的物理地址。
3.wince软件结构里对应MMU的是一个名为OEMAddressTable的数据结构(源文件oeminit.asm中),其中建立了物理地址和虚拟地址的静态映射关系,也可以在其中改动系统所能识别物理内存的大小,以支持大内存。
4.我们也可以在wince启动后调用CreateStaticMapping和NKCreateStaticMapping来实现OEMAddressTable中的这种物理地址和虚拟地址的静态映射关系。
5.建立了静态映射关系的虚拟地址只能由内核模式下的程序来操作,例如 ISR。除非你在定制系统时,选择了full kernal mode,使所有程序都运行在完全内核模式下,这将导致系统不稳定。
6.如果要在驱动程序中访问设备寄存器,必须建立动态虚拟地址映射,可以调用MmmapIoSpace函数来实现,或者通过VirtualAlloc和VirtualCopy函数来实现。其实MmmapIoSpace内部就调用了后者。
7.在驱动中访问虚拟地址时,必须是非缓存段(位于0xA0000000 到 0xBFFFFFFF )。
8.使用VirtualCopy函数映射物理地址时,其lpvSrc参数必须右移8位,且相应的fdwProtect参数必须带page_physical。
9.如果是ARM体系的处理器,访问挂在系统总线上的设备寄存器前,必须先把总线地址转化成CPU的地址,通过HalTranslateBusAddress实现两种物理地址的变换,然后再调用MmmapIoSpace函数作虚实地址的转换。
也可以使用 TransBusAddrToVirtual ()直接把总线上的地址转化成系统的虚拟地址。 在一般的应用程序中访问 I/O 是访问它的缓存段虚拟地址,而驱动中必须访问无缓存段虚拟地址。简单来说无缓存段虚拟地址 = 缓存段虚拟地址 +0x20000000 。
总结起来,如果是 wince 内核(如HAL)访问外部 I/O ,只需要在 OEMAddressTable 中定义物理地址到虚拟地址间的映射关系就可以了;如果是应用程序或者驱动要访问 I/O ,要做的工作包括: 1 。在 CPU 物理地址和虚拟地址间做一个动态映射, 2 。对虚拟地址进行操作。
wince5.0下可以使用CreateBusAccessHandle(总线注册表路径)+BusTransBusAddrToVirtual来实现总线物理地址到系统虚拟地址的直接变换。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -