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