:KITL是PLATFORM BUILDER中的一个亮点,提供了和本地调试类似的断点、变量跟踪、内存查看等手段,如果没有KITL,嵌入式调试应该只能用串口打印消息来看了,工作效率大大下降。本文以实现最简单的SERIAL KITL为目的,就其实现代码进行跟踪调试,这些代码跨越了WINCE的PLATFORM、PUBLIC、PRIVATE三大主要目录,有些烦琐,不过只要能调通,一切工作和弯路都是值得的。我把调试经验和个人理解写下来,希望能帮助别人少走弯路。如果**中有理解失当的地方,请不吝赐教。<br /><br />正文:<br /><br />一.void OEMInit() [platformprojectsrckerneloalinit.c]<br /> 首先从OEMInit()函数看起。。在依次初始化Branch-Target Buffer、KERNEL函数、初始化中断、TIMER之后,就轮到KITL了。调用了这个函数OALKitlStart()。此时有个编译的分支,如果是RELEASE版本,那么在kernelkernstubs.c里面的OALKitlStart()函数是个STUB,只是return TRUE; 如果是DEBUG版本,那就进到kerneloalkitl.c里面的OALKitlStart().<br /><br />二.BOOL OALKitlStart() [platformmyprojectsrckerneloal]<br /> 在OALKitlStart()里面,首先试图从0xA00FF00处读取bootloader里面留下的kitl参量,如果读不到东西则使用该函数里的默认配置。由于原来用了ethernet同时作为download和kitl途径,所以在InitSpecifiedEthDevice函数里给pKitlArgs结构体赋值了。现在想把两者划分清楚,首先就把读取bootloader里面的kitl参量一句干掉,强迫使用我们在下面的默认配置。主要就是填充三个参量<br /><br />1.首先是这个结构体<br />OAL_KITL_ARGS pKITLArgs<br />{<br /> UINT32 flags; //设好ENABLED标志,按需要设POLL标志,但注意一定不要设PASSIVE标志<br /> DEVICE_LOCATION devLoc;<br /> {<br /> DWORD IfcType; //不论ether还是serial,都是internal type =0;<br /> DWORD BusNumber; // =0<br /> DWORD LogicalLoc; //物理地址<br /> PVOID PhysicalLoc; //留做后面=OALPAtoVA(LogicalLoc, FALSE). 真见鬼, 感觉应该和上面的LogicalLoc作用调过来看着比较顺吧?<br /> DWORD Pin; //Ethernet才用的东东<br /> }<br /> union<br /> {<br /> struct<br /> {<br /> UINT32 baudRate; //不用解释了<br /> UINT32 dataBits;<br /> UINT32 stopBits;<br /> UINT32 parity;<br /> }<br /> struct<br /> {<br /> UINT16 mac[3]; //这个也不用解释了<br /> UINT32 ipAddress;<br /> UINT32 ipMask;<br /> UINT32 ipRoute<br /> }<br /> }<br />}<br />2. pszDeviceID. 感觉这名字就是起了好玩, 赋成Walzer应该比较拽,不过还是保留原来赋的AMOISOFT好了, 免得被打.<br />3. 全局变量OAL_KITL_DEVICE g_kitlDevices. 这个东东在kitl.c开头包含的kitl_cfg.h中被赋值, 最主要就是修改g_kitlDevices.pDriver. 这个pDrvier指向一个函数指针列表的结构体,该列表定义了用做kitl模块的初始化、读写、中断、流控制等函数。 g_kitlDevices本身是个二维数组, 可以定义许多设备用做kitl时提供的参数设置, 后面会用一个for来循环判断pKITLArgs的参数和g_kilDevices里面哪个一维数组成员相匹配.<br /><br />这三个参量填充好以后,就可以进到OALKitlInit(pszDeviceID, pKITLArgs, g_kitlDevices)里面了.<br /><br />三.BOOL OALKitlInit( deviceId, pArgs, pDevice) [platformcommonsrccommonkitlkitl.c]<br /> 这个函数先把输入的参量全部用OALMSG打印出来,这个不管。<br /> 重要的是引入了g_kitlState全局变量,开头一句<br /> g_kitlState.pDevice = OALKitlFindDevice(&pArgs->devLoc, pDevice) 这个就是上面所说的从g_kitlDevices里可用设备列表里循环判断,找到选用的设备的匹配函数指针。<br /> 接着把输入参量devicdId和前面填充好的OAL_KITL_ARGS结构COPY到g_kitlState里面<br /> 然后就可以调用KItlInit(TRUE)了,如果前面在FLAG里面设了PASSSIVE标志,现在就是KitlInit(FALSE)了,嘿嘿爽到了吧。<br /><br />四.BOOL KitlInit(BOOL fStartKitl) [privatewinceoscoreos
kkitlethdbg.c]<br /> 太猥琐了,我要用串口,它居然叫ethdbg.c,不给面子。不过是private里面的东东,可远观而不可亵玩焉~~<br /> 这个函数干了三件事:<br />1. 装载了三个全局的函数指针<br />2. 用NewClient注册了三个KITL客户端:<br /> KITL_SVCNAME_DBGMSG //debug message, Debug信息发布通道<br /> KITL_SVCNAME_PPSH //PPshell, 文本控制台界面 <br /> KITL_SVCNAME_KDBG //kernel debug, 内核调试界面 <br /><br />3.由fStartKitl来决定是否启动StartKitl()函数. (这里顺便提一下,按照匈牙利命名法, BOOL变量前面加个b, 但MS的做法我觉得很合理, BOOL变量就是个FLAG嘛, 前面加个f, 把小b留着给BYTE类型用.)<br /><br />五.static BOOL StartKitl(BOOL fInit) [privatewinceoscoreos
kkitlethdbg.c]<br /> 这又是prviate里面的东东。最痛苦的地方开始了。这函数及其子函数将第一次调用OEM自己写的KITL模块初始化、读写程序。是骡子是马,拉出来溜溜就知道啦~<br /> 按顺序看下来,首先判断输入参量是否要启动KITL,并且如果KITLGlobalState里面被打上KITL_ST_DESKTOP_CONNECTED标记的话,那下面的步骤就全免了。当然我们是第一次运行到这里,若这么就跳出的话,俺就马加爵了。<br /><br /> 第一步:<br /> 干的第一件正事就是调用OEMKitlInit(&Kitl). 这个后面详述. 继续把这个函数看完.<br /> OEMKitlInit初始化KITL的硬件抽象层后并把相关指针数据填充给全局变量KITLTRANSPORT Kitl (有没有搞错,为什么不叫g_kitl), 这些工作做完就返回了<br /> StartKitl收货后把Kitl结构整个检查一遍,保证没错后, 马上买单, 把全局变量KITLGlobalState打上个KITL_ST_KITLSTARTED标记. 这才算KITL启动的第一步OK了.<br /><br /> 第二步:<br /> 接下来就是KITLConnectToDesktop(), 进这个函数后就是第一次使用了前面KITL传输介质硬件抽象层里的读写函数了, 这时候就需要调试了. 这个ConnectToDesktop大致就是先Send了一个kITL.....的frame过去,然后polling等待PC端的response, 那边再发个kITL.....的frame过来, 搞得跟地下d打暗号似的, 其实也没什么玄乎的,就是普通的数据包前面加个KTIL专用的HEADER而已. 这个CONNECT成功后,KITLGlobalState里面就加个KITL_ST_DESKTOP_CONNECTED标记了.<br /><br /> 第三步:<br /> set up kernel function pointers, 也没什么,就三个函数指针, 赋完后就KITL_ST_ADAPTER_INITIALIZED了. 其实这个KITLGolbalSate的总共有7个标志,分别是<br />KITL_STARTED, (OK)<br />DESKTOP_CONNECTED, (OK) <br />TIMER_INIT, (?)<br />INT_ENABLED, (POLLING)<br />IST_STARTED, (POLLING)<br />MULTITHREADED, (?)<br />ADAPTER_INITIALIZED. (OK)<br />后面括号里打上OK是到这里已经完成的, 打问号的我还不太清楚什么作用, INT和IST两项,我们用的POLLING所以肯定是不需要了.<br /><br /> 第四步:<br /> 调用SetKernelCommDev设置kernel通过何种介质传送DBGMSG, PPSH和KDBG.<br /> OHYEAH, 我的SERIAL KITL就夭折在这里. 进到SerKernelCommDev(service, CommDevice)函数里看, 它只认CommDevice=KERNEL_COMM_ETHER的情况,而屏蔽了与ETHER并列的SERIAL和PARALLER,直接return FALSE, 下面的事情都不用干了. 而在MS提供的WinCE Documantation里面,这个SetKernelCommDev函数的说明上写着"This function is obsolete and should not be used". 若想改嘛,这个是在private里面的还动它不得. NND, 感觉被MS raped了.<br /> 如果使用ETHER在这里成功的话, 下面还有两个函数NKForceCleanBoot()和KITLInitializeInterrupt()走过去, 这KITL初始化就全部结束了. 我估计KITLGolbalSate里面的INIT_ENABLED和IST_STARTED就是在这个函数过程中被标记上的.<br /> <br /> <br />六.BOOL OEMKitlInit(PKITLTRANSPORT pKitl) [platformcommonsrccommonkitlkitl.c]<br /> 前面提到StartKItl起来后,首要的就是调用OEMKitlInit. 这个函数在WinCE4.2和5.0里差别很大, 4.2里的做法是 if (!InitEther (pKitl) && !InitParallelSerial (pKitl)), 把ETHER, SERIAL, PARALLEL都初始化了一遍,碰运气看哪个用得上,而5.0里是进来后就一个很明显的分支剧情,由g_kitlState.pDevice->type来决定是调用OALKitlEthInit还是OALKitlSerialInit. 典型的种族歧视, 居然没有OALKitlParallelinit. 还好我们用的是SERIAL.<br /> 这里有个选择编译的地方,就是#ifdef KITL_ETHER和#ifdef KITL_SERIAL, 具体定义的地方是该目录下的sources文件里面一行 CDEFINES=$(CDEFINES) -DKITL_SERIAL -DKITL_ETHER, 猥琐啊找了半天. 其实我觉得既然有if结构来选了,那么选择编译也是可有可无的了.<br /> 好,下面就进到OALKItlSerialInit()里面.<br /><br />七.BOOL OALKitlSerialInit(LPSTR deviceId, OAL_KITL_DEVICE *pDevice, OAL_KITL_ARGS *pArgs, KITLTRANSPORT *pKitl) <br /> [platformcommonsrccommonkitlkitlserial.c]<br /> 我自己往这个kitlserial.c文件里写了六个函数.<br /> BOOL KitlSerialInit(KITL_SERIAL_INTFO *pSerInfo)<br /> UINT16 KitlSerialWriteData(UINT8 *pch, UINT16 length)<br /> UINT16 KitlSerialReadData(UINT8 *pch, UINT16 length)<br /> void KitlSerialFlowControl //stub, 我所用的FFUART只有TXD和RXD两根线, RTS等都没有, 所以FlowControl自然也应该是STUB了<br /> void KitlSerialEnableInt(void) //stub, use polling<br /> void KitlSerialDisableInt(void) //stub, use polling<br /> 否则前面的g_kitlDevices里面没有相应的硬件抽象层来填充.<br /> 上面的SerialRecv, Encode, Decode等就意思都很明显了,不用多说. OK现在已经走到最底层了, **也可以结束了.<br /><br />八、记录一下调试经验<br /> 虽然这是我第三次调串口了,由于没总结前面的经验,还是耗了两天才到private里面夭折的地方。实际上应该一天就能走到了。问题出在<br />1. 第一天调试器根本用不上手。调试器软件、PB不断死翘,经常重启软件甚至重启电脑,第一天有3/4以上的时间耗在这些问题上, 不断重启。<br />2. 在UART初始化函数的最后,居然忘记了在interrupt controller register里面enable uart unit, 这么乌龙的事。<br />3. KitlSerialFlowControl的问题. 写的时候照搬了X86下的函数, 没想明白到底要Control什么. 调试时死在这里后, 一开始把指向这个函数的指针设置成NULL, 但这样PRIVATE里面有些要IF判断的函数就进不去了. 后来换成STUB就OK了.<br />4. receive函数里面, 在收每个BYTE之前先去判断了Line Status Register里面的Data Ready bit, 如果该为零, 则返回失败. 但这里是有问题的,具体也没太想明白, 反正在调试debug serial的时候就把这个判断从MS提供的源码里头删去了,现在做KITL serial时又手痒加进去, 果然还是不行. 这可能是MS或INTEL的一处BUG, 但按照INTEL CPU MANUL UPDATE里面给的读流程, 一开始只判断LSR里面的ERROR, 没有判断DR位就开始读第一个BYTE了. 读过后再判断如果DR位为1,则继续读下一BYTE.<br />5. 在receive函数里加通过debugger加break point, 结果receive buffer register里面的数据被debugger扫描去以后,就变零了,CPU上却什么都收不到, 这事情耗了大半个下午,最后还是Jeffery发现的这个问题.<br /><br />参考**:<br />KITL解析 by Nasiry (http://nasiry.cnblogs.com/archive/2004/09/22/45473.html)<br /><br />
|