显示标签为“Windows CE”的博文。显示所有博文
显示标签为“Windows CE”的博文。显示所有博文

2008年7月30日星期三

Sharing Hardware Registers: When to replace multiple writers with a shared resource driver.

Sharing Hardware Registers: When to replace multiple writers with a shared resource driver.
Posted by Jeremy Cooke
翻译自
http://blogs.msdn.com/ce_base/archive/2007/03/29/sharing-hardware-registers-when-to-replace-multiple-writers-with-a-shared-resource-driver.aspx

Hi, I am cpuwolf。这篇来自微软的建议,写的是关于多个驱动共享同一个硬件的问题。这里给出了很好的建议。我尝试用尽可能贴切的专业词语翻译,有什么不对的地方,请指出。
当设计复杂的系统时,偶尔会必需协调对某些的共享资源的访问。一般这些同步问题都由操作系统所提供的例如critical section或mutex等来解决 。这些技术在它是一个简单的共享资源,并且较少的对它的访问时,是无可挑剔的。但对于复杂的资源访问多个threads ,进程,驱动程序,或多个应用程序,新的规范可能更为合适。一个很好的共享资源的驱动程序的例子,就是GPIO驱动程序。每个驱动可能共用一个GPIO模块的寄存器,或者共同读-修改-写,每个驱动如果不同步,那么就会产生问题,
比起直接访问共享资源,把这些访问操作抽象到一个独立的驱动程序去做,可能会更好。所有会访问共享资源的家伙,可以采用尽量少的open这个驱动或者较少使用它的接口。同时,这个驱动程序在内部去处理将同步问题和处理任何可能出现的错误。调用者可以不用再担心同步问题,因为所有这些麻烦都已经交给了别的驱动。
通过使用这一技术,程序员,往往能够避免使用critical section,否则这样的结构会充斥整个原本的代码。现在每一次对共享资源的访问,现在只需通过一个用户设计的API,这些API抽象了访问动作,并且增加程序的可读性。
共享资源的驱动程序也应该预处理和检查错误的输入,以及很好的处理共享资源的错误。由于错误码现在是集中处理的,所有程序员大可认为共享资源已经被很好的保护。这是team开发时的很重要的一点。
例子学习
这个嵌入式系统具有多通道ADC转换器,并且每个通道都有相应的设备使用。多个不相干的驱动程序都试图访问各自的通道。软件可以将hardware所有通道的转换结果放在一块内存中。如果多个线程请求ADC转换,同时没有适当的同步 那么这里就潜藏着一个污染到另一个的情况。
在这特别的系统里有和ADC转换相关的battery驱动程序, USB驱动程序,以及系统的背光驱动程序。battery驱动程序是要关心当前的电压、电池的温度、以及充电电压和充电电流。 USB驱动程序需要监控USB vbus的电压,而背光驱动器需监测环境光线与光电二极管。由于这些功能是在自己的驱动中处理,所有共享资源的访问,都必须以一个更先进的方式控制。以下图1描绘了每个驱动程序都要存取硬件的ADC 。
模拟向数字转换器,将被抽象,并使用一个专门的驱动程序。驱动程序允许多个writer使用所有通道的转换功能,同时返回结果放在一个用户指定的内存中(见图2下文) 。
这个驱动暴露出一个函数,其中包括A / D转换功能并返回结果。如下:
enum ADChannels
{
AD_CHANNEL_BAT_VOLTAGE = 0,

AD_CHANNEL_BAT_CHG_VOLTAGE,
AD_CHANNEL_BAT_CHG_CURRENT,

AD_CHANNEL_BAT_TEMP,
AD_CHANNEL_USB_VBUS,
AD_CHANNEL_PHOTODIODE,
TOTAL_AD_CHANNELS

};


BOOL PerformADConversion(USHORT *pOutBuffer, DWORD OutSize);
在这里pOutBuffer 指向用户的buffer,在函数返回的大小必须等于TOTAL_AD_CHANNELS。必须注意,当向用户数据区拷贝数据时,建议使用类似CeSafeCopyMemory 的函数来处理,它可以处理非法内存的问题。
该PerformADConversion 函数可以实例化一个critical section同步使用,可以看出在图3 :

在这个设计中,所有硬件访问都被保护在一个集中的代码区,只使用仅仅一个critical section。系统性能得以提升,因为使用了轻量级的critical section,而是一个缓慢的同步对象(如mutex) 。程序员现在可以进一步优化系统性能,每个驱动程序可以同时进行。
虽然使用一个专门的驱动程序来完成这些事情有很多的好处,但是,事实上,这种做法可能会稍微有点慢。因为通过标准的Windows CE的函数访问驱动程序是有一些开销。因此,最好的方法是建立一种结构是驱动的访问开销最小。但是无论怎样,使用这种方法的收益是远远大于这里的开销的。

挖掘WinCE 5系统调用过程

int shellcode[] =
{
0xE59F0014, // ldr r0, [pc, #20]
0xE59F4014, // ldr r4, [pc, #20]
0xE3A01000, // mov r1, #0
0xE3A02000, // mov r2, #0
0xE3A03000, // mov r3, #0
0xE1A0E00F, // mov lr, pc
0xE1A0F004, // mov pc, r4
0x0101003C, // IOCTL_HAL_REBOOT
0xF000FE74, // trap address of KernelIoControl
};
这是黑客常用的缓冲攻击代码形式,这里的32为常数,都是ARM机器指令。
我就要想你展示的不是如何攻击WinCE内核,而是看看WinCE的系统调用是如何实现的。WinCE一个常用的API,KernelIoControl,它的实现体实在内核NK.exe,而function caller只是一个trusted的user级别的application。
你可以想象,这样的API的实现,必然要经历CPU从user processer mode切换到supervisor processer mode。
BOOL KernelIoControl(
DWORD dwIoControlCode,
LPVOID lpInBuf,
DWORD nInBufSize,
LPVOID lpOutBuf,
DWORD nOutBufSize,
LPDWORD lpBytesReturned
);
KernelIoControl超过了4个参数,那么在参数传递时,超过的部分使用堆栈完成的。IOCTL_HAL_REBOOT的参数都没什么用,所以这个我们忽略,传0就可以了,因此r1,r2,r3赋值0.
IOCTL_HAL_REBOOT的数值必须放在寄存器r0。
上面的代码中r4的内容会变为0xF000FE74,代码最后就是跳转到0xF000FE74,你可以看到这个地址很大,在整个内存空间的高地址。同时这个地址是经过编码得到,公式是:
0xf0010000-(256*apiset+apinr)*4
对于KernelIoControl,apiset是0,apinr是99。0xF000FE74是这样得到的。
当代码跳转的这样一个高地址时,会引发prefetch abort,这样exception会被内核 ,也就是NK.exe抓到。然后再对这个出错地址进行解析,就可以知道应用程序想访问那个system cal。
综上,WinCE并不是靠SWI这样的软中断实现system call,而是prefetch abort。

2008年7月24日星期四

WinCE5 kernel高地址都放了什么东西


typedef struct ARM_HIGH {
ulong firstPT[4096]; // 0xFFFD0000: 1st level page table
char reserved2[0x20000-0x4000];

char exVectors[0x400]; // 0xFFFF0000: exception vectors
char reserved3[0x2400-0x400];

char intrStack[0x400]; // 0xFFFF2400: interrupt stack
char reserved4[0x4900-0x2800];

char abortStack[0x700]; // 0xFFFF4900: abort stack
char reserved5[0x6800-0x5000];

char fiqStack[0x100]; // 0xFFFF6800: FIQ stack
char reserved6[0xC000-0x6900];

char kStack[0x800]; // 0xFFFFC000: kernel stack
struct KDataStruct kdata; // 0xFFFFC800: kernel data page
} ARM_HIGH;

Post-Processing flash.dio


windows mobile最终生成image通常叫做flash.bin。请记住其实是flash.dio,这个文件是一个真正的数据镜像,flash.bin是flash.dio在后期处理,也就是post-processing阶段生产的。
Flash.bin中会包含NAND flash每个sector info的附加信息,并且以block的大小分段。其实也就是把flash.dio中的原始数据加以分段,打包。

值得注意的是微软上面提到:
In addition, NandPostProc.exe also truncates TFAT by 4 blocks, and then adds two compaction blocks into IMGFS and into TFAT. These compaction blocks provide wear–leveling, and help lengthen the life of the flash hardware.

Flash.bin中imgfs分区和tfat分区会被加上gap,反正我是没理解微软的用意。他这样一改,会导致开头的MBR中的分区表信息和这里对不上,因为两个分区后移了。除非这4块区域被标记为未使用,那么查找分区时会自然向后查找,否则就会出错。下面是我用KITL抓到的错误:
Loaded 'cecompr.dll', no matching symbolic information found.
2928 PID:a7a9f512 TID:e7a7a85e ERROR: IMGFS!CVolume::LoadCompressionEngine: unable to load decompressor type "yyyyyyyyyyyyyyyy" from dll "CECOMPR.DLL"... expect failures!!!
Unloaded symbols for 'cecompr.dll'

这样的错误,我花了1个多月,才发现就是上面4 blocks导致。最后我临时换了方案,改烧flash.dio就OK。
所以如果你在porting platform时,遇到这样的问题,先参考一下我的经历。

2007年7月26日星期四

Windows Mobile bus driver

Windows Mobile的驱动程序程序实际是一个树状结构。Device.exe
负责加载几乎所有的驱动程序,而驱动程序的各种信息是存在于注册表里面。在系统启动?
锥危琩evice.exe是不知道有多少驱动是要加载的。他只是找到bus driver
,然后由它继续加载所有的驱动,所以bus driver是很重要的驱动程序,是桥梁驱动。
注册表中,HKEY_LOCAL_MACHINE\Drivers的键值RootKey指示了bus driver
的位置,一般你会看到Drivers\BuiltIn。那么HKEY_LOCAL_MACHINE\Drivers\BuiltIn下
Dll说明的是bus driver的名称。
微软的Ak中是默认有个驱动的BusEnum.dll(在public下有源代码)。这个驱动会枚举
BuiltIn下的所有键值,将这些驱动都加载进device.exe。
bus driver OEM是可以替换的。你此时可能回想既然已经有现成的,为什么还要自己写?
在很多时候,SOC的许多module的clock
开关都会放在一个统一的寄存器组里,那么驱动程序在想开/关这些寄存器时,都要操作?
桓黾拇嫫髯椋庵止蚕碜试吹姆梦适强赡芤鸪逋坏摹?

每个驱动要访问这个寄存器组,他们也就需要映射这个物理空间。如果每个驱动都映射,?
驮斐尚槟饪占涞募罄朔选?
那么如何完成自定义的bus driver
?微软为我们提供了很完善的类库,使我们很容易做到。Class DefaultBusDriver 和
Class DeviceFolder就是这样基础类。详细的代码细节自己看吧。Bus driver
是也是标准的流驱动。它需要在xxx_Init中引发一系列枚举动作,将驱动加载进来。

2007年6月19日星期二

Windows Mobile 5 虚拟内存空间危机

虚拟内存空间这个概念我们常常听见,也有很多人能说出个一二。但是其实真正能理解这个概念的人并不多。今天就以Windows Mobile 5这个嵌入式操作系统理解一下什么叫Virtual Memory Space。

Windows Mobile 5的虚拟内存模型如下图:

我们会看见这样的概念:kernel space 和 user space。看似简单,你真的理解么?分析这样的memory space概念的时候一定分清视角,通俗说就是你是以user进程的角度,还是以微软操作系统内核的角度看这个空间。这点非常重要。不信你试试看。

0x8000,0000到0xFFFF,FFFF这个叫内核空间,也就是只有内核代码可以用这样的地址,他们是高特权级的区域。你的应用程序是访问不到的。如果你要“硬”来,操作系统一定会把你destroy掉,并给你一个类似非法访问的警告提示。除非你是“黑客”,用类似“缓冲溢出”等攻击方法来访问。

那么对于0x8000,0000以下的空间,叫做用户空间。什么?你做就知道了?!哈哈!那我问你,用户空间是不是只有用户程序可以访问的空间?你要是回答是,那就错了。用户空间是用户程序可以访问的空间,但不是“只有”。内核是老大,他当然也可以访问。所以你看,我们定义user space的概念一般只是针对user来说,而定义kernel space只是针对kernel来说,如果你只是单单把user space和kernel space放在一起,实际上没什么可比性。

小总结一下,kernel可以访问整个4GB空间,而user“最多”只能直接访问下2GB空间。我这里又多强调了一个“最多”。下面将详细讲解。

“最多”也就是说用户程序不能整个空间都是他的,一个多任务分时操作系统上同时有很多的用户的程序在“奔跑”,而且彼此独立,互不打扰。内存空间这样宝贵的资源,内核不可能让某个进程独占,因此,Windows Mobile5将下2GB空间均匀划分成64块,每一块32MB,并且slot0~slot32用于用户程序,slot32-slot63用于shared memory,所以WM5最多能有32个进程同时在运行。我们可以看见,每个单独的process是有可怜的32MB的大小。这个数值才是我们真正该关心的东西。每个进程的内存是不允许其他空间随便访问的。可怜可怜,你只有有32MB可以“使用”。“使用”这个概念再落实一下,就是如你调用VirtualAlloc()这样的内存分配函数。

我这篇文章叫“危机”,不是光介绍一下这些概念。想想,像device.exe这样的“进程大户”,身上挂装了n多的DLL,而且每个驱动程序都要类似调用VirtualAlloc的函数来映射寄存器空间,也就是CPU的整个寄存器空间几乎都要映射到这个狭小的32MB空间里,自己本身还有很多的代码与数据要占用空间,这个进程真是很吃力呀。随着SOC(system on chip)的茁壮成长,一个chip里集成的module数量越来越多,随之而来的寄存器的增多,也就是寄存器组空间的增大,Windows Mobile5将不再可以使用。

幸好2007年我们迎来了Windows CE 6,虚拟空间的问题得以解决。可是微软相继发布的CrossBow主要是UI的变化,内核依然采用windows CE 5。不知什么时候微软会发布使用CE6内核的Windows Mobile。

我们现在还要继续使用Windows CE5 kernel,那就要想办法解决这个问题。

前面提到slot32-slot63用于shared memory,如果能使用这块空间,那么虚拟内存不足的问题就可以解决。微软告诉我们mapped file是使用这块空间的,所以解决方案自然是
h_map=CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, Size, NULL);
VirBase=MapViewOfFile(h_map, FILE_MAP_WRITE, 0, 0, 0);

这样分配下来的虚拟地址会在0x4200,0000-0x7E00,0000区域内。此时只有虚拟空间被分配,实际的物理内存还并没有映射。因此这个时候你不能类似这样:

*(volatile DWORD *)VirBase=123;

写内存,这个动作会分配实际的物理内存,从而直接导致下面的VirtualCopy的失败。

这样虚拟空间有了,实际的物理内存可以这样映射


VirtualCopy((void*)VirBase, (void *)(PhyBase >> 8), fbSize, PAGE_READWRITE | PAGE_NOCACHE | PAGE_PHYSICAL)

这个三个函数的结合使用,使我们可以充分利用user memory 的空间。

2007年6月13日星期三

Windows Mobile文件系统相关注册表设置

在工作过程中,很多同事都会问起文件系统注册表的设定。今天我就总结一下贴在这里。下面是一个典型的注册表的设置:

[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\SMFLASH]

"Prefix"="DSK"

"Dll"="ONDisk.dll"

"Index"=dword:1

"Order"=dword:10

"Profile"="SMFLASH"

"IClass"="{A4E7EDDA-E575-4252-9D6B-4195D48BB865}"

"BmlVolumeId"=dword:0

"BmlPartitionId"=dword:3

[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\SMFLASH]

"DefaultFileSystem"="IMGFS"

"PartitionDriver"="mspart.dll"

"AutoMount"=dword:1

"AutoPart"=dword:1

"AutoFormat"=dword:1

"Name"="Microsoft Flash Disk"

"Folder"="Storage"

[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\SMFLASH\IMGFS]

"MountHidden"=dword:1

"MountAsROM"=dword:1

"XIP"=dword:0

[HKEY_LOCAL_MACHINE\System\StorageManager\Profiles\SMFLASH\FATFS]

"Flags"=dword:10

"FormatTfat"=dword:1

"CheckForFormat"=dword:1

"EnableWriteBack"=dword:1

"MountAsBootable"=dword:1

"MountAsRoot"=dword:1

红色的是经常被质疑的部分,他们虽然在微软的document上可以找到相关解释,可是还是有很多歧义,让我们一起仔细看看。下面我会用到很多PC的概念来帮助大家理解。

第一段代码是说明“硬盘”驱动是那个DLL。Profile键值说明profile的名字,也就是【HKEY_LOCAL_MACHINE\System\StorageManager\Profiles】之后跟着的名字,这个名字你随便取,只要不超过7个字符都可以。其实profile可以理解为在“硬盘”驱动之上的软件层,属于文件系统这个领域的概念。而第一段是驱动程序领域的概念。

第二段是这个profile的总体的信息,他一定会有分区的概念,所以我们需要mspart.dll来识别分区信息。

"AutoMount"=dword:1

"AutoPart"=dword:1

"AutoFormat"=dword:1

这几个你就一起用,没关系的。我知道你担心什么。那就是担心分区的内容会不会每次重启都会重建,分区内容会丢失。实际上不会的。Autoformat是当发现分区的文件系统没有格式化时,才回去format,反之,如果已经formated,那么就不会有任何动作。Autopart是在你的profile没有分区时,系统自动帮你分区,不过他没有那么智能,只能将整个profile分成一个区,从头到尾。当然同上,如果你已经有分区信息,系统不会做任何动作。Automount是如果分区没有安装,就会自动安装。这3个键值,你就一起把他们加上,肯定没问题。

第三段定义了profile之上的特定分区imgfs分区,这是一定要有的分区。MountHidden是将这个分区安装为不可见。这样你在资源管理器里就不会看见这个目录。MountAsROM是以只读方式安装,这样分区的文件是不可写的。

第四段代码是真正的用户空间,是fat文件系统。FORMATTFAT是强制以tfat文件系统格式化,tfat比fat更加的强壮。CheckForFormat键值是可选的,加上之后,这个分区在每次重启是系统都会用

IOCTL_HAL_QUERY_FORMAT_PARTITION 的ioctl来检查是否需要格式话。如果没有这个键值,文件系统就不会调用这个ioctl。

"MountAsBootable"=dword:1

"MountAsRoot"=dword:1

这两个键值一般都一起用,就是指明是该分区是root file system。

好了,到此就基本说完了。如果有什么问题可以mail我。

2007年5月30日星期三

dio nb0 bin的关系


在wm533中,生成的image的扩展名很多,dio,nb0,bin等等,他们都是什么关系。看一个例子。
Memory.cfg.xml的内容如下

我们可以看见image由4个分区组成,分别为ULDR,NK,OS,Storage(最多只能有4个分区)。这4个分区都放在一个NAND flash上面,叫做FLASH,所以build完整个image会生成一个flash.dio。
Flash.dio完全是一个flash的镜像,如果用于工厂的量产,可以直接映射在NAND flash上面用。
同时会生成flash.dio.nb0,看似只多了一个扩展名,可是内容就完全不同了,这是为第三方的flash用的,会在每个512字节后面,也是是一个扇区后面,添加8个字节的sectorinfo。所以它会比flash.dio大。要强调的是,这个文件用flash.dio处理而来。
最后会生成flash.bin,它由flash.dio.nb0处理而来,这个文件是有格式的,也就是说有文件头,文件信息之类。
总结一下,flash.dio -> flash.dio.nb0 -> flash.bin,如此的生成过程。

2007年5月8日星期二

微软引起的while不归路

如果你是windows mobile的驱动程序开发人员,又恰好写了类似的这样一句话。
while(dev->devState!=5);
说明一下,dev是个结构体指针。你希望等待它的成员变量变成5,继续执行。同时

有可能你在中断服务程序中改变这个成员变量的值。
哈哈!我幸运的告诉你你已经进入了“死循环”,永远跳不出去啦。

这是个编译器优化产生的问题,看看编译器产生的汇编片段。
|$L39064|
cmp r3, #5
bne |$L39064|
它将dev->devState优化为了一个寄存器r3,这样的比较是跳不出循环的。

那么要解决这个问题就是将dev这个指针声明为volatile,之后,看编译器产生的汇编片段,变成了
|$L39064|
ldr r3, [r6, #4]
cmp r3, #5
bne |$L39064|
这才是我们想要的结果。汗!!

我在WM533做的实验,确实有这个问题。我觉得这个优化,微软做的太离谱了。不知道算不算编译器bug

2007年3月12日星期一

[新手上路]在你的WinCE应用程序中映射硬件物理地址

在你的WinCE应用程序中映射硬件物理地址,下面是个例子

void * MapPhys(UINT32 addr,int size)
{
BYTE * Buffer=NULL;

//分配size大小虚拟地址空间
Buffer = (BYTE *)VirtualAlloc(0, size, MEM_RESERVE, PAGE_NOACCESS);
if(!Buffer)
{
MessageBox(NULL, _T("mem map error"), _T("Error"), MB_OK|MB_TOPMOST);
goto cleanup;
}
//映射物理地址(传给内核的地址总是要右移8位)
if (!VirtualCopy(Buffer, (void *)(addr>>8),size,PAGE_READWRITE | PAGE_PHYSICAL | PAGE_NOCACHE))
{
MessageBox(NULL, _T("mem copy error"), _T("Error"), MB_OK|MB_TOPMOST);
goto cleanup;
}
cleanup:
return (void*)Buffer;
}


这个函数返回的地址你就可以用了。Good luck!

2007年3月9日星期五

Wince shell 电池指示器反应慢?

Windows Mobile桌面上的电池图标有时会更新的太慢,我们知道可以用
hevent = CreateEvent(NULL, FALSE, FALSE, TEXT("SSUpdatePower"));
SetEvent(hevent);
CloseHandle(hevent);
来强制shell去更新电池状态。可是你会发现即时这样做,有时还是达不到目的。
为什么?
shell必然是调用GetSystemPowerStatusEx或GetSystemPowerStatusEx2来询问电池状态,这两个函数都用一个共同的参数BOOL fUpdate,据我了解到,shell正是传了FALSE进这个函数,结果导致,读到的是battery驱动中cache的信息,因此会表现出反应慢。
那么我们可以在battery驱动中强制调用GetSystemPowerStatusEx(TRUE),来达到目的