KITL 解析 - KITL是怎样工作的?
作者:Nasiry 来源:blog 时间:2007年10月16日 5:57 阅读1474次
by nasiry
注:由于我们主要是分析kitl的工作原理我们就电源管理的代码不做分析,以加电启动的程序流进行分析。
Part 1. kitl初始化
Kitl的加载于其他调试服务之前,以提供为这些调试服务发布调试信息和接收主机调试命令的的通道。通常kitl在系统HAL初始化工作完成后进行加载,MS建议在OEMInit中启动kitl。这样就可以使用NIC或者是serial/Pal作为kitl的物理传输介质。
kitl的初始化由KitlInit完成,这部分代码主要负责:(to be fill later)
下面我们来看看kitl的具体代码,这些代码位于%CEROOT%\PRIVATE\WINCEOS\COREOS\NK\KITL下。
BOOL KitlInit (BOOL fStartKitl)
{
// just initialize function pointers
pKITLRegisterDfltClient = KITLRegisterDfltClient;
pKITLIoCtl = KITLIoctl;
pfnIsDesktopDbgrExist = IsDesktopDbgrExist;
// Initialize default clients
NewClient (KITL_SVC_DBGMSG, KITL_SVCNAME_DBGMSG, FALSE);
NewClient (KITL_SVC_PPSH, KITL_SVCNAME_PPSH, FALSE);
NewClient (KITL_SVC_KDBG, KITL_SVCNAME_KDBG, FALSE);
return fStartKitl? StartKitl (TRUE) : TRUE;
}
这段代码主要完成两个动作:
1.装载函数指针,为后续代码的执行装载入口点。
2.注册kitl客户端,这些客户端实现传输层以后就是我们所需要的调试界面。
输入参数决定是否立即启动KITL服务,如果false的话就仅仅进行初始化等待后续动作使用startKitl来启动kitl.
我们再来看看NewClient的原型
static PKITL_CLIENT NewClient (UCHAR uId, LPCSTR pszSvcName, BOOL fAlloc)
{
DEBUGCHK(IS_VALID_ID(uId));
DEBUGCHK (!KITLClients[uId]);
if (!fAlloc) {
DEBUGCHK(IS_DFLT_SVC(uId));
KITLClients[uId] = &DfltClnts[uId];
} else if (!(KITLClients[uId] = (PKITL_CLIENT) AllocMem (HEAP_KITLCLIENT))) {
return NULL;
}
memset (KITLClients[uId], 0, sizeof(KITL_CLIENT));
KITLClients[uId]->ServiceId = uId;
strcpy (KITLClients[uId]->ServiceName, pszSvcName);
return KITLClients[uId];
}
这个被称为NewClient的函数所完成的功能十分的简单,先检查所需要创建的结构是否是系统默认服务所需要的,如果是的话就直接将该结构的指针指向全局结构DfltClnts并初始化结构,如果不是就申请相应的空间完成该结构的初始化。默认的服务有 KITL_SVCNAME_DBGMSG, KITL_SVCNAME_PPSH, KITL_SVCNAME_KDBG分别对应Debug信息的发布通道(Debug message),文本控制台界面(PPshell),和内核调试界面(kernel debug),在这里大家可能会问:为什么不统一使用固定的全局结构来存放这些服务的信息呢?原因很简单,因为这些"client"在WindowSCE下是可以注册扩充和注销的,这样用AllocMem所分配的内存空间在不再需要这些服务的时候可以释放掉,就可以避免不必要的浪费。另外KITLClients是这样定义的PKITL_CLIENT KITLClients[MAX_KITL_CLIENTS];所以kitl所能注册的client连同3个默认的服务一共最多可以有MAX_KITL_CLIENTS--128个。
下面继续沿着程序流往下看吧,kitlInit完成最基本的初始化动作即可启动kitl服务了。再看一下这个函数的原型。
static BOOL StartKitl (BOOL fInit)
{
// KITL already started?
if (!fInit && (KITLGlobalState & KITL_ST_DESKTOP_CONNECTED)) {
return TRUE;
}
/*
* When this function is called, the kernel hasn't yet been initialized,
* so can't make any system calls. Once the system has come up far
* enough to handle system calls, KITLInitializeInterrupt() is called to complete
* initialization. This is indicated by the KITL_ST_MULTITHREADED flag in KITLGlobalState.
*/
// Detect/initialize ethernet hardware, and return address information
if (!OEMKitlInit (&Kitl))
return FALSE;
// verify that the Kitl structure is initialized.
if (!Kitl.pfnDecode || !Kitl.pfnEncode || !Kitl.pfnEnableInt || !Kitl.pfnRecv || !Kitl.pfnSend
|| !Kitl.dwPhysBuffer || !Kitl.dwPhysBufLen || !Kitl.WindowSize || !Kitl.pfnGetDevCfg || !Kitl.pfnSetHostCfg) {
return FALSE;
}
// Validate that address is not in free RAM area - the HAL should put it in a reserved
// section of memory conditional on some environment var.
if ((pTOC->ulRAMStart < Kitl.dwPhysBuffer + Kitl.dwPhysBufLen)
&& (pTOC->ulRAMEnd > Kitl.dwPhysBuffer)) {
KITLOutputDebugString("\r\n!Debug Ethernet packet buffers in free RAM area - must set IMGEBOOT=1\r\n");
return FALSE;
}
if (Kitl.dwPhysBufLen < (DWORD) 3 * KITL_BUFFER_POOL_SIZE) {
KITLOutputDebugString("\r\n!Debug Ethernet buffer size too small, must be at least 0x%x bytes (3 * WindowSize * 2 * KITL_MTU)\r\n",
3 * KITL_BUFFER_POOL_SIZE);
return FALSE;
}
KITLGlobalState |= KITL_ST_KITLSTARTED; // indicate (to kdstub) that KITL has started
// If the initialized flag is already set, we are being called from the power on routine,
// so reinit the HW, but not any state.
if (!(KITLGlobalState & KITL_ST_ADAPTER_INITIALIZED)) {
// perform the initial handshake with the desktop
if (!KITLConnectToDesktop ()) {
KITLOutputDebugString ("\r\n!Unable to establish KITL connection with desktop!\r\n");
return FALSE;
}
// Set up kernel function pointers
pKITLInitializeInterrupt = KITLInitializeInterrupt;
pKITLSend = KITLSend;
pKITLRecv = KITLRecv;
KITLGlobalState |= KITL_ST_ADAPTER_INITIALIZED;
if (Kitl.dwBootFlags & KITL_FL_DBGMSG)
SetKernelCommDev (KERNEL_SVC_DBGMSG, KERNEL_COMM_ETHER);
if (Kitl.dwBootFlags & KITL_FL_PPSH)
SetKernelCommDev (KERNEL_SVC_PPSH, KERNEL_COMM_ETHER);
if (Kitl.dwBootFlags & KITL_FL_KDBG)
SetKernelCommDev (KERNEL_SVC_KDBG, KERNEL_COMM_ETHER);
// only perform cleanboot if it's connected at boot. Cleanboot flag is
// ignored if it's started dynamically.
if (fInit && (Kitl.dwBootFlags & KITL_FL_CLEANBOOT)) {
extern ROMHDR *const volatile pTOC; // Gets replaced by RomLoader with real address
// just clear the magic nOEMKitlInitumber (see SC_SetCleanRebootFlag)
// NOTE: We can NOT call SC_SetCleanRebootFlag here since logPtr isn't
// initialized yet.
((fslog_t *)((pTOC->ulRAMFree + MemForPT) | 0x20000000))->magic1 = 0;
}
// if OEM calls KitlInit (FALSE), KITLInitializeInterrupt will
// not be called in SystemStartupFuc. We need to initialize
// interrupt here (when RegisterClient is called)
if (fKITLcsInitialized && !InSysCall ()) {
KITLInitializeInterrupt ();
}
}
LOG (KITLGlobalState);
return TRUE;
}
启动代码首先判断是否已经启动kitl服务,之后调用OEMKitlInit,该函数并不在private目录下实现,通常windowsCE需要用户定制的代码都是这种结构---MS提供的代码接口,用户自己完成相应的OEM部分,通常这些代码都是与具体的硬件平台相关的代码。kitl的OEM代码在HAL中实现,通常在platform\kernel\hal\.下,这部分的代码我们先跳过,看完startkitl的全貌再回过头逐个说明。OEMkitlInit为kitl初始化硬件传输介质,同时分配初始化一些kitl所需要的全局结构。随后startkitl继续检查OEMkitlInit所分配和初始化的KITL结构和内存区域是否有效后设置kitl的全局标示KITL_ST_KITLSTARTED;之后设置终端服务程序以及接收和发送程序的入口点后设置全局标示KITL_ST_ADAPTER_INITIALIZED。现在传输介质已经全部就绪,通过SetKernelCommDev设置kernel通过ethernet传送调试信息,调试输入,以及CESH控制台。再后调用KITLInitializeInterrupt完成中断的初始化kitl启动的过程就结束了。
紧接着我们来看看,OEMkitlInit都须要我们干什么。下面用SMDK2440的kitl为实例来进行分析:
BOOL OEMKitlInit (PKITLTRANSPORT pKitl)
{
KITLOutputDebugString ("+OEMKitlInit\n");
RETAILMSG(1, (_T("+OEMKitlInit\r\n")));
// try to find a transport available
if (!InitEther (pKitl)
&& !InitParallelSerial (pKitl)) {
KITLOutputDebugString ("Unable to initialize KITL Transports!\n");
return FALSE;
}
gpKitl = pKitl;
KITLOutputDebugString ("-OEMKitlInit\n");
RETAILMSG(1, (_T("-OEMKitlInit\r\n")));
return TRUE;
}
事实上工作很简单,调用InitEther (pKitl) 和 !InitParallelSerial (pKitl)初始化网卡直接把初始化的KITL全局结构返回就是所有的工作。这儿的InitParallelSerial是一个dummy永远返回false,也就是说这里没有对serial¶llel transport进行支持。真正的工作量集中在InitEther之后。事实上InitEther 和 InitParallelSerial只要任意的实现一个就可以达到建立传输界面的目的.下面,我们继续看后面的代码。
BOOL InitEther(PKITLTRANSPORT pKitl)
{
EDBG_ADAPTER adp;
DWORD dwDHCPLeaseTime;
DWORD dwSubnetMask;
KITLOutputDebugString ("+InitEther\n");
memset (&adp, 0, sizeof(adp));
memset (pKitl, 0, sizeof (KITLTRANSPORT));
// use existing code for ether initialization
if (!OEMEthInit (&adp))
return FALSE;
// we are going to completely ignore the info in bootargs and the adaptor info
// returned from OEMEthInit, except MAC address. Just to prove that KITL will connect standalone
// get the MAC address
MyAddr.wMAC[0] = adp.Addr.wMAC[0];
MyAddr.wMAC[1] = adp.Addr.wMAC[1];
MyAddr.wMAC[2] = adp.Addr.wMAC[2];
//MyAddr = adp.Addr;
CreateDeviceName(&MyAddr, pKitl->szName);
KITLOutputDebugString ("Using device name: %s\n", pKitl->szName);
// If we haven't been given an IP address from our loader (or if we're not using static IP), get an IP address
// from a DHCP server.
if (adp.Addr.dwIP)
{
// Static IP or we got the IP from our bootloader...
MyAddr.dwIP = adp.Addr.dwIP;
dwSubnetMask = 0; // Don't care about subnet mask...
dwDHCPLeaseTime = adp.DHCPLeaseTime;
}
else
{
// Get a DHCP address...
if (!EbootGetDHCPAddr (&MyAddr, &dwSubnetMask, &dwDHCPLeaseTime))
return FALSE;
}
MyAddr.wPort = htons (EDBG_SVC_PORT);
KITLOutputDebugString ("Device %s, IP %s, Port %d\n", pKitl->szName, inet_ntoa (MyAddr.dwIP), htons (MyAddr.wPort));
// initialize KITL Ethernet transport layer
if (!KitlEtherInit (&MyAddr, dwDHCPLeaseTime)) {
KITLOutputDebugString ("Unable to initialize KITL Ether transport\n");
return FALSE;
}
// fill in the blanks in KITLTRANSPORT structure.
pKitl->FrmHdrSize = KitlEtherGetFrameHdrSize ();
pKitl->Interrupt = (UCHAR) adp.SysIntrVal;
pKitl->dwPhysBuffer = EDBG_PHYSICAL_MEMORY_START;
pKitl->dwPhysBufLen = 0x20000; // 128K of buffer available
pKitl->dwBootFlags = 0;
pKitl->WindowSize = EDBG_WINDOW_SIZE;
pKitl->pfnDecode = KitlEtherDecodeUDP;
pKitl->pfnEncode = KitlEtherEncodeUDP;
pKitl->pfnSend = EthSend;
pKitl->pfnRecv = OEMEthGetFrame;
pKitl->pfnEnableInt = KitlEthEnableInts;
pKitl->pfnSetHostCfg = SetHostCfg;
pKitl->pfnGetDevCfg = GetDevCfg;
KITLOutputDebugString ("-InitEther\n");
return TRUE;
}
这个函数完成的工作主要是调用OEMEthInit初始化网卡的服务程序及获得相应的IP和MAC,如果IP无效则用DHCP动态获得IP.通过MAC值产生一个标示,这个标示用来给PB的IDE使用。刚才的我们在kitlInit中看到除了检查OEMkitlInit的返回值之外还检查了KITL结构,该结构的这些特征值正是在这儿设置的。在这儿可以看到pKitl->pfnDecode pKitl->pfnEncode pKitl->pfnSetHostCfg pKitl->pfnGetDevCfg 以及kitl所用的中断号这些都是OEM代码,也就是用于传输的编码和解码形式以及配置函数都是可以自己定义的,这样一来也就无所谓使用什么传输介质作为KITK
的transport了,这就为使用1394或者是USB这一类的传输链路也能充当传输界面作了准备。 OEMEthInit函数是用于初始化传输介质--以太网卡。这部分代码直接是硬件控制代码,我们来简单的看一下。
BOOL
OEMEthInit(EDBG_ADAPTER *pAdapter)
{
PBYTE pBaseIOAddress;
// Driver globals from the bootloader.
//
if (pDriverGlobals->eth.EbootMagicNum == EBOOT_MAGIC_NUM)
{
memcpy(pAdapter, &pDriverGlobals->eth.TargetAddr, sizeof(EDBG_ADAPTER));
switch(pDriverGlobals->misc.EbootDevice)
{
case(DOWNLOAD_DEVICE_PCMCIA): // NE2000 CF card.
pBaseIOAddress = (PBYTE)PCMCIA_Init();
if (pBaseIOAddress)
{
// Initialize the built-in Ethenet controller.
//
if (!NE2000Init((PBYTE)pBaseIOAddress, 1, pAdapter->Addr.wMAC))
{
EdbgOutputDebugString("ERROR: OEMEthInit: Failed to initialize Ethernet controller.\r\n");
return(FALSE);
}
}
pfnEDbgInit = NE2000Init;
pfnEDbgEnableInts = NE2000EnableInts;
pfnEDbgDisableInts = NE2000DisableInts;
pfnEDbgGetPendingInts = NE2000GetPendingInts;
pfnEDbgGetFrame = NE2000GetFrame;
pfnEDbgSendFrame = NE2000SendFrame;
pfnEDbgReadEEPROM = NE2000ReadEEPROM;
pfnEDbgWriteEEPROM = NE2000WriteEEPROM;
pfnEDbgSetOptions = NE2000SetOptions;
#ifdef IMGSHAREETH
pfnCurrentPacketFilter = Ne2000CurrentPacketFilter;
pfnMulticastList = NE2000MulticastList;
#endif // IMGSHAREETH.
break;
case(DOWNLOAD_DEVICE_CS8900): // CS8900A.
// Initialize the CS8900.
//
if (!CS8900DBG_Init((PBYTE)CS8900DBG_IOBASE, CS8900DBG_MEMBASE, pAdapter->Addr.wMAC))
{
EdbgOutputDebugString("ERROR: OEMEthInit: CS8900 initialization failed.\r\n");
return(FALSE);
}
pfnEDbgInit = CS8900DBG_Init;
pfnEDbgEnableInts = CS8900DBG_EnableInts;
pfnEDbgDisableInts = CS8900DBG_DisableInts;
pfnEDbgGetFrame = CS8900DBG_GetFrame;
pfnEDbgSendFrame = CS8900DBG_SendFrame;
pfnEDbgGetPendingInts = CS8900DBG_GetPendingInts;
#ifdef IMGSHAREETH
pfnCurrentPacketFilter = CS8900DBG_CurrentPacketFilter;
pfnMulticastList = CS8900DBG_MulticastList;
#endif // IMGSHAREETH.
break;
default:
EdbgOutputDebugString("ERROR: OEMInit: Unknown download NIC (0x%x).\r\n", pDriverGlobals->misc.EbootDevice);
return(FALSE);
}}
else
{
// TODO - retrieve CS8900 MAC address from flash...
// TODO - intialize the CS8900 from scratch...
}
EdbgOutputDebugString("::: OEMEthInit() IP Address : %s\r\n", inet_ntoa(pAdapter->Addr.dwIP));
EdbgOutputDebugString("::: OEMEthInit() Netmask : %s\r\n", inet_ntoa(pDriverGlobals->eth.SubnetMask));
if (pDriverGlobals->misc.EbootDevice == DOWNLOAD_DEVICE_PCMCIA)
pAdapter->SysIntrVal = SYSINTR_PCMCIA_LEVEL;
else
pAdapter->SysIntrVal = SYSINTR_ETHER;
pAdapter->DHCPLeaseTime = DEFAULT_DHCP_LEASE;
pAdapter->EdbgFlags = pDriverGlobals->eth.EdbgFlags;
#ifdef IMGSHAREETH
VBridgeInit();
VBridgeKSetLocalMacAddress((char *)pAdapter->Addr.wMAC);
#endif // IMGSHAREETH.
return(TRUE);
}
这个函数看起来很复杂其实真正的工作并不多,首先判断是不是由eboot启动的,如果已经eboot中已经完成了对以太网卡的初始化动作就直接使用网卡并装载/挂接网卡所需的函数和网卡信息,否则就需要自己设置网卡的MAC地址和初始化网卡(事实上以上函数并没有对这部分代码进行实现,这就是很多使用2410/2440的用户在不使用eboot启动的情况下总是不能使用kitl的原因--找不到eboot在DriverGlobal中留下的magic NUMBER)。这儿之所以有NE2000和Cs8900的区分是因为SMDK2440可以使用PCMICA挂接Ne2000兼容的NIC或板载CS8900,后面设置中断标示有两个分支也是这个原因。
IMGSHAREETH的部分是Vbridge的部分,为什么要使用这个叫vbridge的东西呢?我们看看下面的假设。
为了建立kitl占用了一个网卡资源,而该资源如果在windowsCE下复用(该设备同时被两个驱动使用1.kitl 2.windowsCE NIC driver)的话会不会导致问题呢?看看下面的两个函数。
VBridgeInit();
VBridgeKSetLocalMacAddress((char *)pAdapter->Addr.wMAC);
该函数在内核调试传输通道和tcp/ip&windsock之间建立一个虚拟的网桥v-bridge,在外部看来vbridge就像在mac层一样,vbridge一方面和硬件通讯建立传输的物理界面,另一方面和调试所需的EDBG和vmini.dll提供相同的逻辑界面,在更高的层面vmini.dll就像一个专门的网卡一样支持NDIS以至于tcp/ip协议栈。这样我们就可以一方面使用网卡做调试另外一方面仍然能让windowsCE使用网卡通讯,对于windowCE而言所使用的网卡不在是与底层对应的网络设备,而是通过vbridge虚拟出来的网络设备,所以在直接使用SMDK24XX的bsp编译出来的系统网卡显示为vmini就是这个原因。这个时候网卡驱动怎么配置呢?答案很简单,就是不要网卡驱动,因为我们已经从vbridge中抽象(虚拟---用一个网卡)出一个网卡了,原来的网卡驱动也就不在需要了。
从上面的OemKitlInit到InitEther都是OEM代码,目的在于使Kitl与相应的transport的物理介质联系起来,也就是构建kitl的硬件抽象层,在一个kitl初始化代码中中只有这部分工作(OEM代码)是必要的,其余的代码直接使用MS编译好的lib就可以了。尽管如此我们还是继续看下面的代码,虽然这对我们来说不是必须的不过对一个程序要有全面的认识,框架上的重要模块都是需要了解和认识的。
看完了这一系列的OEM代码,继续看看StartKitl里面我们没有看完的部分在设置启动标示位之前做了2个检查分别是检查buffer的位置是否在编译系统的时候预留下来以及是否有足够的长度可用。这个内存区域不是动态分配的,而是在bsp的内存配置文件中指定并保留下来的(见bsp file目录下的config.bib)。再下来进行一个KITLConnectToDesktop的动作,这个看名字就知道作用了。就是和PC连接。同样看看代码:
static BOOL KITLConnectToDesktop (void)
{
// we'll use the PpfsFmtBuf for send buffer since ppfs can't be started yet at this stage
//
PKITL_HDR pHdr = (PKITL_HDR) (PpfsFmtBuf + Kitl.FrmHdrSize);
PKITL_DEV_TRANSCFG pCfg = (PKITL_DEV_TRANSCFG) KITLDATA(pHdr);
USHORT cbData = sizeof (PpfsFmtBuf) - Kitl.FrmHdrSize - sizeof (KITL_HDR) - sizeof (KITL_DEV_TRANSCFG);
if (!Kitl.pfnGetDevCfg ((LPBYTE) (pCfg+1), &cbData))
return FALSE;
memset (pHdr, 0, sizeof (KITL_HDR));
pHdr->Id = KITL_ID;
pHdr->Service = KITL_SVC_ADMIN;
pHdr->Cmd = KITL_CMD_TRAN_CONFIG;
cbData += sizeof (KITL_DEV_TRANSCFG);
pCfg->wLen = cbData;
pCfg->wCpuId = KITL_CPUID;
memcpy (pCfg->szDevName, Kitl.szName, KITL_MAX_DEV_NAMELEN);
cbData += sizeof (KITL_HDR);
return KitlSendFrame (PpfsFmtBuf, cbData)
&& KITLPollResponse (FALSE, ChkCnxDsktp, TranCnxDsktp, (LPVOID) cbData);
}
结构PKITL_HDR就是kilt的传输头格式,而PKITL_DEV_TRANSCFG信息则是传输设备的设置。首先通过调用Kitl.pfnGetDevCfg得到传输设备的信息,Kitl.pfnGetDevCfg是函数指针,在对以太网卡初始化的时候指向OEM代码中的GetDevCfg函数。通过这个函数得到设备信息(smdk2410的bsp中这儿返回的就是IP地址)。然后再继续设置传输头的标示,类型,命令等等信息,然后就是发送数据了,具体的动作就是调用KitlSendFrame。(To be continue...)
BOOL KitlSendFrame (LPBYTE pbFrame, WORD cbData)
{
if (!Kitl.pfnEncode (pbFrame, cbData)) {
KITLOutputDebugString ("!KitlSendFrame: transport failed to encode the data frame\r\n");
return FALSE;
}
return KitlSendRawData (pbFrame, (USHORT) (cbData + Kitl.FrmHdrSize + Kitl.FrmTlrSize));
}
这个函数首先调用KitlEtherEncodeUDP对数据帧进行编码为UDP协议需要的格式。然后调用 KitlSendRawData将数据送出至PC.
BOOL KitlSendRawData (LPBYTE pbData, WORD wLength)
{
BOOL fRet;
if (!(KITLGlobalState & KITL_ST_MULTITHREADED) || InSysCall())
fRet = Kitl.pfnSend (pbData, wLength);
else if (IsDesktopDbgrExist ())
fRet = KCall((PKFN) Kitl.pfnSend, pbData, wLength);
else {
EnterCriticalSection (&KITLKCallcs);
fRet = Kitl.pfnSend (pbData, wLength);
LeaveCriticalSection (&KITLKCallcs);
}
return fRet;
}
首先判定系统没有在调度中且当前代码在不可剥夺状态状态运行,通过EthSend调用OEMEthSendFrame将数据送出就完成了工作。另外两个分支与我们分析的程序流没有关系先放一下。
BOOL
OEMEthSendFrame(
BYTE *pData, // IN - Data buffer
DWORD dwLength) // IN - Length of buffer
{
int retries = 0;
while (retries++ < 4) {
if (!pfnEDbgSendFrame(pData, dwLength))
{
#ifdef IMGSHAREETH
ProcessVMiniSend();
#endif //IMGSHAREETH
return TRUE;
}
else
EdbgOutputDebugString("!OEMEthSendFrame failure, retry %u\n",retries);
}
return FALSE;
}
在发送数据帧的过程中专门有处理vMini发送的过程。由于kitl本身就不是很简单我们以后后面再用专门的文章说明vbridge的工作过程。完成了发送,我们继续下面的过程。
BOOL KITLPollResponse (BOOL fUseSysCalls, PFN_CHECK pfnCheck, PFN_TRANSMIT pfnTransmit, LPVOID pData)
{
DWORD dwLoopCnt = 0, dwLoopMax = MIN_POLL_ITER;
DWORD dwStartTime = CurMSec;
int nTimeMax = MIN_POLL_TIME; // start with 1 sec
BOOL fUseIter = FALSE, fUseTick = FALSE;
while (!pfnCheck (pData)) {
//
// if we've already connected with desktop, use the desktop
// "Retransmit" package to determine if we need to retransmit
//
if (!(KITLGlobalState & KITL_ST_DESKTOP_CONNECTED)) {
if (fUseTick) {
if ((int) (CurMSec - dwStartTime) > nTimeMax) {
// retransmit
if (!pfnTransmit (pData, fUseSysCalls))
return FALSE;
dwStartTime = CurMSec;
if (nTimeMax < MAX_POLL_TIME)
nTimeMax <<= 1;
}
} else if (fUseIter || (dwStartTime == CurMSec)) {
// if time isn't moving for a while, we'll
// use iteration.
if (dwLoopCnt ++ > dwLoopMax) {
if (!pfnTransmit (pData, fUseSysCalls))
return FALSE;
if (dwLoopMax < MAX_POLL_ITER)
dwLoopMax <<= 1;
dwLoopCnt = 0;
fUseIter = TRUE;
}
} else {
// time is moving, just use tick from here
fUseTick = TRUE;
}
}
if (!KITLPollData(fUseSysCalls, pfnTransmit, pData)) {
return FALSE;
}
}
return TRUE;
}
static BOOL TranCnxDsktp (LPVOID pParam, BOOL fUseSysCalls)
{
return KitlSendFrame (PpfsFmtBuf, (WORD) (DWORD) pParam);
}
这个函数的主体是一个循环,终止的条件是!pfnCheck (pData),这个函数是由前面传递进来的,返回值为KITLGlobalState & KITL_ST_DESKTOP_CONNECTED,也就是说在得到桌面连接之前是不会返回的也就是说启动kitl以后不与桌面计算机连接windowsCE的是无法启动的。由于从dwStartTime定义到dwStartTime == CurMSec的判定仅仅需要很少的时间就可以完成这段时间内CurMSec是不会被改写的就可以通过循环进行超时检查
并通过TranCnxDsktp重新发送数据,直到KITLPollData设置KITLGlobalState。
static BOOL KITLPollData(BOOL fUseSysCalls, PFN_TRANSMIT pfnTransmit, LPVOID pData)
{
LPBYTE pRecvBuf = PollRecvBuf;
if (fUseSysCalls && (KITLGlobalState & KITL_ST_MULTITHREADED)
&& !(pRecvBuf = _alloca(KITL_MTU))) {
KITLOutputDebugString("!KITLPollData: STACK OVERFLOW!\r\n");
return FALSE;
}
HandleRecvInterrupt(pRecvBuf, fUseSysCalls, pfnTransmit, pData);
return TRUE;
}
由于我们上面传来的fUseSysCalls参数为false所以前一段检查操作并不进行。直接调用 HandleRecvInterrupt。
void HandleRecvInterrupt(UCHAR *pRecvBuf, BOOL fUseSysCalls, PFN_TRANSMIT pfnTransmit, LPVOID pData)
{
WORD wLen = KITL_MTU;
BOOL fFrameRecvd;
// Receive data into buffer
do {
if (!fUseSysCalls)
fFrameRecvd = Kitl.pfnRecv (pRecvBuf, &wLen);
else if (IsDesktopDbgrExist ())
fFrameRecvd = KCall((PKFN) Kitl.pfnRecv, pRecvBuf, &wLen);
else {
EnterCriticalSection (&KITLKCallcs);
fFrameRecvd = Kitl.pfnRecv (pRecvBuf, &wLen);
LeaveCriticalSection (&KITLKCallcs);
}
if (fFrameRecvd) {
ProcessRecvFrame (pRecvBuf,wLen,fUseSysCalls, pfnTransmit, pData);
wLen = KITL_MTU;
}
} while (fFrameRecvd);
}
通过Kitl.pfnRecv调用pfnEDbgGetFrame指向的CS8900DBG_GetFrame读取当前的数据帧送交ProcessRecvFrame处理。注意,这儿的pfnEDbgGetFrame并不是通过DMA或者是网卡传来的中断启动的而是使用查询的方法进行的,这就是为什么这儿并没有启动中断仍然能够使用以太网卡进行数据传输数据的原因。随后,我们将计算机端送来的数据送交ProcessRecvFrame处理。
static BOOL ProcessRecvFrame(UCHAR *pFrame, WORD wMsgLen, BOOL fUseSysCalls, PFN_TRANSMIT pfnTransmit, LPVOID pData)
{
作者:Nasiry 来源:blog 时间:2007年10月16日 5:57 阅读1474次
by nasiry
注:由于我们主要是分析kitl的工作原理我们就电源管理的代码不做分析,以加电启动的程序流进行分析。
Part 1. kitl初始化
Kitl的加载于其他调试服务之前,以提供为这些调试服务发布调试信息和接收主机调试命令的的通道。通常kitl在系统HAL初始化工作完成后进行加载,MS建议在OEMInit中启动kitl。这样就可以使用NIC或者是serial/Pal作为kitl的物理传输介质。
kitl的初始化由KitlInit完成,这部分代码主要负责:(to be fill later)
下面我们来看看kitl的具体代码,这些代码位于%CEROOT%\PRIVATE\WINCEOS\COREOS\NK\KITL下。
BOOL KitlInit (BOOL fStartKitl)
{
// just initialize function pointers
pKITLRegisterDfltClient = KITLRegisterDfltClient;
pKITLIoCtl = KITLIoctl;
pfnIsDesktopDbgrExist = IsDesktopDbgrExist;
// Initialize default clients
NewClient (KITL_SVC_DBGMSG, KITL_SVCNAME_DBGMSG, FALSE);
NewClient (KITL_SVC_PPSH, KITL_SVCNAME_PPSH, FALSE);
NewClient (KITL_SVC_KDBG, KITL_SVCNAME_KDBG, FALSE);
return fStartKitl? StartKitl (TRUE) : TRUE;
}
这段代码主要完成两个动作:
1.装载函数指针,为后续代码的执行装载入口点。
2.注册kitl客户端,这些客户端实现传输层以后就是我们所需要的调试界面。
输入参数决定是否立即启动KITL服务,如果false的话就仅仅进行初始化等待后续动作使用startKitl来启动kitl.
我们再来看看NewClient的原型
static PKITL_CLIENT NewClient (UCHAR uId, LPCSTR pszSvcName, BOOL fAlloc)
{
DEBUGCHK(IS_VALID_ID(uId));
DEBUGCHK (!KITLClients[uId]);
if (!fAlloc) {
DEBUGCHK(IS_DFLT_SVC(uId));
KITLClients[uId] = &DfltClnts[uId];
} else if (!(KITLClients[uId] = (PKITL_CLIENT) AllocMem (HEAP_KITLCLIENT))) {
return NULL;
}
memset (KITLClients[uId], 0, sizeof(KITL_CLIENT));
KITLClients[uId]->ServiceId = uId;
strcpy (KITLClients[uId]->ServiceName, pszSvcName);
return KITLClients[uId];
}
这个被称为NewClient的函数所完成的功能十分的简单,先检查所需要创建的结构是否是系统默认服务所需要的,如果是的话就直接将该结构的指针指向全局结构DfltClnts并初始化结构,如果不是就申请相应的空间完成该结构的初始化。默认的服务有 KITL_SVCNAME_DBGMSG, KITL_SVCNAME_PPSH, KITL_SVCNAME_KDBG分别对应Debug信息的发布通道(Debug message),文本控制台界面(PPshell),和内核调试界面(kernel debug),在这里大家可能会问:为什么不统一使用固定的全局结构来存放这些服务的信息呢?原因很简单,因为这些"client"在WindowSCE下是可以注册扩充和注销的,这样用AllocMem所分配的内存空间在不再需要这些服务的时候可以释放掉,就可以避免不必要的浪费。另外KITLClients是这样定义的PKITL_CLIENT KITLClients[MAX_KITL_CLIENTS];所以kitl所能注册的client连同3个默认的服务一共最多可以有MAX_KITL_CLIENTS--128个。
下面继续沿着程序流往下看吧,kitlInit完成最基本的初始化动作即可启动kitl服务了。再看一下这个函数的原型。
static BOOL StartKitl (BOOL fInit)
{
// KITL already started?
if (!fInit && (KITLGlobalState & KITL_ST_DESKTOP_CONNECTED)) {
return TRUE;
}
/*
* When this function is called, the kernel hasn't yet been initialized,
* so can't make any system calls. Once the system has come up far
* enough to handle system calls, KITLInitializeInterrupt() is called to complete
* initialization. This is indicated by the KITL_ST_MULTITHREADED flag in KITLGlobalState.
*/
// Detect/initialize ethernet hardware, and return address information
if (!OEMKitlInit (&Kitl))
return FALSE;
// verify that the Kitl structure is initialized.
if (!Kitl.pfnDecode || !Kitl.pfnEncode || !Kitl.pfnEnableInt || !Kitl.pfnRecv || !Kitl.pfnSend
|| !Kitl.dwPhysBuffer || !Kitl.dwPhysBufLen || !Kitl.WindowSize || !Kitl.pfnGetDevCfg || !Kitl.pfnSetHostCfg) {
return FALSE;
}
// Validate that address is not in free RAM area - the HAL should put it in a reserved
// section of memory conditional on some environment var.
if ((pTOC->ulRAMStart < Kitl.dwPhysBuffer + Kitl.dwPhysBufLen)
&& (pTOC->ulRAMEnd > Kitl.dwPhysBuffer)) {
KITLOutputDebugString("\r\n!Debug Ethernet packet buffers in free RAM area - must set IMGEBOOT=1\r\n");
return FALSE;
}
if (Kitl.dwPhysBufLen < (DWORD) 3 * KITL_BUFFER_POOL_SIZE) {
KITLOutputDebugString("\r\n!Debug Ethernet buffer size too small, must be at least 0x%x bytes (3 * WindowSize * 2 * KITL_MTU)\r\n",
3 * KITL_BUFFER_POOL_SIZE);
return FALSE;
}
KITLGlobalState |= KITL_ST_KITLSTARTED; // indicate (to kdstub) that KITL has started
// If the initialized flag is already set, we are being called from the power on routine,
// so reinit the HW, but not any state.
if (!(KITLGlobalState & KITL_ST_ADAPTER_INITIALIZED)) {
// perform the initial handshake with the desktop
if (!KITLConnectToDesktop ()) {
KITLOutputDebugString ("\r\n!Unable to establish KITL connection with desktop!\r\n");
return FALSE;
}
// Set up kernel function pointers
pKITLInitializeInterrupt = KITLInitializeInterrupt;
pKITLSend = KITLSend;
pKITLRecv = KITLRecv;
KITLGlobalState |= KITL_ST_ADAPTER_INITIALIZED;
if (Kitl.dwBootFlags & KITL_FL_DBGMSG)
SetKernelCommDev (KERNEL_SVC_DBGMSG, KERNEL_COMM_ETHER);
if (Kitl.dwBootFlags & KITL_FL_PPSH)
SetKernelCommDev (KERNEL_SVC_PPSH, KERNEL_COMM_ETHER);
if (Kitl.dwBootFlags & KITL_FL_KDBG)
SetKernelCommDev (KERNEL_SVC_KDBG, KERNEL_COMM_ETHER);
// only perform cleanboot if it's connected at boot. Cleanboot flag is
// ignored if it's started dynamically.
if (fInit && (Kitl.dwBootFlags & KITL_FL_CLEANBOOT)) {
extern ROMHDR *const volatile pTOC; // Gets replaced by RomLoader with real address
// just clear the magic nOEMKitlInitumber (see SC_SetCleanRebootFlag)
// NOTE: We can NOT call SC_SetCleanRebootFlag here since logPtr isn't
// initialized yet.
((fslog_t *)((pTOC->ulRAMFree + MemForPT) | 0x20000000))->magic1 = 0;
}
// if OEM calls KitlInit (FALSE), KITLInitializeInterrupt will
// not be called in SystemStartupFuc. We need to initialize
// interrupt here (when RegisterClient is called)
if (fKITLcsInitialized && !InSysCall ()) {
KITLInitializeInterrupt ();
}
}
LOG (KITLGlobalState);
return TRUE;
}
启动代码首先判断是否已经启动kitl服务,之后调用OEMKitlInit,该函数并不在private目录下实现,通常windowsCE需要用户定制的代码都是这种结构---MS提供的代码接口,用户自己完成相应的OEM部分,通常这些代码都是与具体的硬件平台相关的代码。kitl的OEM代码在HAL中实现,通常在platform\kernel\hal\.下,这部分的代码我们先跳过,看完startkitl的全貌再回过头逐个说明。OEMkitlInit为kitl初始化硬件传输介质,同时分配初始化一些kitl所需要的全局结构。随后startkitl继续检查OEMkitlInit所分配和初始化的KITL结构和内存区域是否有效后设置kitl的全局标示KITL_ST_KITLSTARTED;之后设置终端服务程序以及接收和发送程序的入口点后设置全局标示KITL_ST_ADAPTER_INITIALIZED。现在传输介质已经全部就绪,通过SetKernelCommDev设置kernel通过ethernet传送调试信息,调试输入,以及CESH控制台。再后调用KITLInitializeInterrupt完成中断的初始化kitl启动的过程就结束了。
紧接着我们来看看,OEMkitlInit都须要我们干什么。下面用SMDK2440的kitl为实例来进行分析:
BOOL OEMKitlInit (PKITLTRANSPORT pKitl)
{
KITLOutputDebugString ("+OEMKitlInit\n");
RETAILMSG(1, (_T("+OEMKitlInit\r\n")));
// try to find a transport available
if (!InitEther (pKitl)
&& !InitParallelSerial (pKitl)) {
KITLOutputDebugString ("Unable to initialize KITL Transports!\n");
return FALSE;
}
gpKitl = pKitl;
KITLOutputDebugString ("-OEMKitlInit\n");
RETAILMSG(1, (_T("-OEMKitlInit\r\n")));
return TRUE;
}
事实上工作很简单,调用InitEther (pKitl) 和 !InitParallelSerial (pKitl)初始化网卡直接把初始化的KITL全局结构返回就是所有的工作。这儿的InitParallelSerial是一个dummy永远返回false,也就是说这里没有对serial¶llel transport进行支持。真正的工作量集中在InitEther之后。事实上InitEther 和 InitParallelSerial只要任意的实现一个就可以达到建立传输界面的目的.下面,我们继续看后面的代码。
BOOL InitEther(PKITLTRANSPORT pKitl)
{
EDBG_ADAPTER adp;
DWORD dwDHCPLeaseTime;
DWORD dwSubnetMask;
KITLOutputDebugString ("+InitEther\n");
memset (&adp, 0, sizeof(adp));
memset (pKitl, 0, sizeof (KITLTRANSPORT));
// use existing code for ether initialization
if (!OEMEthInit (&adp))
return FALSE;
// we are going to completely ignore the info in bootargs and the adaptor info
// returned from OEMEthInit, except MAC address. Just to prove that KITL will connect standalone
// get the MAC address
MyAddr.wMAC[0] = adp.Addr.wMAC[0];
MyAddr.wMAC[1] = adp.Addr.wMAC[1];
MyAddr.wMAC[2] = adp.Addr.wMAC[2];
//MyAddr = adp.Addr;
CreateDeviceName(&MyAddr, pKitl->szName);
KITLOutputDebugString ("Using device name: %s\n", pKitl->szName);
// If we haven't been given an IP address from our loader (or if we're not using static IP), get an IP address
// from a DHCP server.
if (adp.Addr.dwIP)
{
// Static IP or we got the IP from our bootloader...
MyAddr.dwIP = adp.Addr.dwIP;
dwSubnetMask = 0; // Don't care about subnet mask...
dwDHCPLeaseTime = adp.DHCPLeaseTime;
}
else
{
// Get a DHCP address...
if (!EbootGetDHCPAddr (&MyAddr, &dwSubnetMask, &dwDHCPLeaseTime))
return FALSE;
}
MyAddr.wPort = htons (EDBG_SVC_PORT);
KITLOutputDebugString ("Device %s, IP %s, Port %d\n", pKitl->szName, inet_ntoa (MyAddr.dwIP), htons (MyAddr.wPort));
// initialize KITL Ethernet transport layer
if (!KitlEtherInit (&MyAddr, dwDHCPLeaseTime)) {
KITLOutputDebugString ("Unable to initialize KITL Ether transport\n");
return FALSE;
}
// fill in the blanks in KITLTRANSPORT structure.
pKitl->FrmHdrSize = KitlEtherGetFrameHdrSize ();
pKitl->Interrupt = (UCHAR) adp.SysIntrVal;
pKitl->dwPhysBuffer = EDBG_PHYSICAL_MEMORY_START;
pKitl->dwPhysBufLen = 0x20000; // 128K of buffer available
pKitl->dwBootFlags = 0;
pKitl->WindowSize = EDBG_WINDOW_SIZE;
pKitl->pfnDecode = KitlEtherDecodeUDP;
pKitl->pfnEncode = KitlEtherEncodeUDP;
pKitl->pfnSend = EthSend;
pKitl->pfnRecv = OEMEthGetFrame;
pKitl->pfnEnableInt = KitlEthEnableInts;
pKitl->pfnSetHostCfg = SetHostCfg;
pKitl->pfnGetDevCfg = GetDevCfg;
KITLOutputDebugString ("-InitEther\n");
return TRUE;
}
这个函数完成的工作主要是调用OEMEthInit初始化网卡的服务程序及获得相应的IP和MAC,如果IP无效则用DHCP动态获得IP.通过MAC值产生一个标示,这个标示用来给PB的IDE使用。刚才的我们在kitlInit中看到除了检查OEMkitlInit的返回值之外还检查了KITL结构,该结构的这些特征值正是在这儿设置的。在这儿可以看到pKitl->pfnDecode pKitl->pfnEncode pKitl->pfnSetHostCfg pKitl->pfnGetDevCfg 以及kitl所用的中断号这些都是OEM代码,也就是用于传输的编码和解码形式以及配置函数都是可以自己定义的,这样一来也就无所谓使用什么传输介质作为KITK
的transport了,这就为使用1394或者是USB这一类的传输链路也能充当传输界面作了准备。 OEMEthInit函数是用于初始化传输介质--以太网卡。这部分代码直接是硬件控制代码,我们来简单的看一下。
BOOL
OEMEthInit(EDBG_ADAPTER *pAdapter)
{
PBYTE pBaseIOAddress;
// Driver globals from the bootloader.
//
if (pDriverGlobals->eth.EbootMagicNum == EBOOT_MAGIC_NUM)
{
memcpy(pAdapter, &pDriverGlobals->eth.TargetAddr, sizeof(EDBG_ADAPTER));
switch(pDriverGlobals->misc.EbootDevice)
{
case(DOWNLOAD_DEVICE_PCMCIA): // NE2000 CF card.
pBaseIOAddress = (PBYTE)PCMCIA_Init();
if (pBaseIOAddress)
{
// Initialize the built-in Ethenet controller.
//
if (!NE2000Init((PBYTE)pBaseIOAddress, 1, pAdapter->Addr.wMAC))
{
EdbgOutputDebugString("ERROR: OEMEthInit: Failed to initialize Ethernet controller.\r\n");
return(FALSE);
}
}
pfnEDbgInit = NE2000Init;
pfnEDbgEnableInts = NE2000EnableInts;
pfnEDbgDisableInts = NE2000DisableInts;
pfnEDbgGetPendingInts = NE2000GetPendingInts;
pfnEDbgGetFrame = NE2000GetFrame;
pfnEDbgSendFrame = NE2000SendFrame;
pfnEDbgReadEEPROM = NE2000ReadEEPROM;
pfnEDbgWriteEEPROM = NE2000WriteEEPROM;
pfnEDbgSetOptions = NE2000SetOptions;
#ifdef IMGSHAREETH
pfnCurrentPacketFilter = Ne2000CurrentPacketFilter;
pfnMulticastList = NE2000MulticastList;
#endif // IMGSHAREETH.
break;
case(DOWNLOAD_DEVICE_CS8900): // CS8900A.
// Initialize the CS8900.
//
if (!CS8900DBG_Init((PBYTE)CS8900DBG_IOBASE, CS8900DBG_MEMBASE, pAdapter->Addr.wMAC))
{
EdbgOutputDebugString("ERROR: OEMEthInit: CS8900 initialization failed.\r\n");
return(FALSE);
}
pfnEDbgInit = CS8900DBG_Init;
pfnEDbgEnableInts = CS8900DBG_EnableInts;
pfnEDbgDisableInts = CS8900DBG_DisableInts;
pfnEDbgGetFrame = CS8900DBG_GetFrame;
pfnEDbgSendFrame = CS8900DBG_SendFrame;
pfnEDbgGetPendingInts = CS8900DBG_GetPendingInts;
#ifdef IMGSHAREETH
pfnCurrentPacketFilter = CS8900DBG_CurrentPacketFilter;
pfnMulticastList = CS8900DBG_MulticastList;
#endif // IMGSHAREETH.
break;
default:
EdbgOutputDebugString("ERROR: OEMInit: Unknown download NIC (0x%x).\r\n", pDriverGlobals->misc.EbootDevice);
return(FALSE);
}}
else
{
// TODO - retrieve CS8900 MAC address from flash...
// TODO - intialize the CS8900 from scratch...
}
EdbgOutputDebugString("::: OEMEthInit() IP Address : %s\r\n", inet_ntoa(pAdapter->Addr.dwIP));
EdbgOutputDebugString("::: OEMEthInit() Netmask : %s\r\n", inet_ntoa(pDriverGlobals->eth.SubnetMask));
if (pDriverGlobals->misc.EbootDevice == DOWNLOAD_DEVICE_PCMCIA)
pAdapter->SysIntrVal = SYSINTR_PCMCIA_LEVEL;
else
pAdapter->SysIntrVal = SYSINTR_ETHER;
pAdapter->DHCPLeaseTime = DEFAULT_DHCP_LEASE;
pAdapter->EdbgFlags = pDriverGlobals->eth.EdbgFlags;
#ifdef IMGSHAREETH
VBridgeInit();
VBridgeKSetLocalMacAddress((char *)pAdapter->Addr.wMAC);
#endif // IMGSHAREETH.
return(TRUE);
}
这个函数看起来很复杂其实真正的工作并不多,首先判断是不是由eboot启动的,如果已经eboot中已经完成了对以太网卡的初始化动作就直接使用网卡并装载/挂接网卡所需的函数和网卡信息,否则就需要自己设置网卡的MAC地址和初始化网卡(事实上以上函数并没有对这部分代码进行实现,这就是很多使用2410/2440的用户在不使用eboot启动的情况下总是不能使用kitl的原因--找不到eboot在DriverGlobal中留下的magic NUMBER)。这儿之所以有NE2000和Cs8900的区分是因为SMDK2440可以使用PCMICA挂接Ne2000兼容的NIC或板载CS8900,后面设置中断标示有两个分支也是这个原因。
IMGSHAREETH的部分是Vbridge的部分,为什么要使用这个叫vbridge的东西呢?我们看看下面的假设。
为了建立kitl占用了一个网卡资源,而该资源如果在windowsCE下复用(该设备同时被两个驱动使用1.kitl 2.windowsCE NIC driver)的话会不会导致问题呢?看看下面的两个函数。
VBridgeInit();
VBridgeKSetLocalMacAddress((char *)pAdapter->Addr.wMAC);
该函数在内核调试传输通道和tcp/ip&windsock之间建立一个虚拟的网桥v-bridge,在外部看来vbridge就像在mac层一样,vbridge一方面和硬件通讯建立传输的物理界面,另一方面和调试所需的EDBG和vmini.dll提供相同的逻辑界面,在更高的层面vmini.dll就像一个专门的网卡一样支持NDIS以至于tcp/ip协议栈。这样我们就可以一方面使用网卡做调试另外一方面仍然能让windowsCE使用网卡通讯,对于windowCE而言所使用的网卡不在是与底层对应的网络设备,而是通过vbridge虚拟出来的网络设备,所以在直接使用SMDK24XX的bsp编译出来的系统网卡显示为vmini就是这个原因。这个时候网卡驱动怎么配置呢?答案很简单,就是不要网卡驱动,因为我们已经从vbridge中抽象(虚拟---用一个网卡)出一个网卡了,原来的网卡驱动也就不在需要了。
从上面的OemKitlInit到InitEther都是OEM代码,目的在于使Kitl与相应的transport的物理介质联系起来,也就是构建kitl的硬件抽象层,在一个kitl初始化代码中中只有这部分工作(OEM代码)是必要的,其余的代码直接使用MS编译好的lib就可以了。尽管如此我们还是继续看下面的代码,虽然这对我们来说不是必须的不过对一个程序要有全面的认识,框架上的重要模块都是需要了解和认识的。
看完了这一系列的OEM代码,继续看看StartKitl里面我们没有看完的部分在设置启动标示位之前做了2个检查分别是检查buffer的位置是否在编译系统的时候预留下来以及是否有足够的长度可用。这个内存区域不是动态分配的,而是在bsp的内存配置文件中指定并保留下来的(见bsp file目录下的config.bib)。再下来进行一个KITLConnectToDesktop的动作,这个看名字就知道作用了。就是和PC连接。同样看看代码:
static BOOL KITLConnectToDesktop (void)
{
// we'll use the PpfsFmtBuf for send buffer since ppfs can't be started yet at this stage
//
PKITL_HDR pHdr = (PKITL_HDR) (PpfsFmtBuf + Kitl.FrmHdrSize);
PKITL_DEV_TRANSCFG pCfg = (PKITL_DEV_TRANSCFG) KITLDATA(pHdr);
USHORT cbData = sizeof (PpfsFmtBuf) - Kitl.FrmHdrSize - sizeof (KITL_HDR) - sizeof (KITL_DEV_TRANSCFG);
if (!Kitl.pfnGetDevCfg ((LPBYTE) (pCfg+1), &cbData))
return FALSE;
memset (pHdr, 0, sizeof (KITL_HDR));
pHdr->Id = KITL_ID;
pHdr->Service = KITL_SVC_ADMIN;
pHdr->Cmd = KITL_CMD_TRAN_CONFIG;
cbData += sizeof (KITL_DEV_TRANSCFG);
pCfg->wLen = cbData;
pCfg->wCpuId = KITL_CPUID;
memcpy (pCfg->szDevName, Kitl.szName, KITL_MAX_DEV_NAMELEN);
cbData += sizeof (KITL_HDR);
return KitlSendFrame (PpfsFmtBuf, cbData)
&& KITLPollResponse (FALSE, ChkCnxDsktp, TranCnxDsktp, (LPVOID) cbData);
}
结构PKITL_HDR就是kilt的传输头格式,而PKITL_DEV_TRANSCFG信息则是传输设备的设置。首先通过调用Kitl.pfnGetDevCfg得到传输设备的信息,Kitl.pfnGetDevCfg是函数指针,在对以太网卡初始化的时候指向OEM代码中的GetDevCfg函数。通过这个函数得到设备信息(smdk2410的bsp中这儿返回的就是IP地址)。然后再继续设置传输头的标示,类型,命令等等信息,然后就是发送数据了,具体的动作就是调用KitlSendFrame。(To be continue...)
BOOL KitlSendFrame (LPBYTE pbFrame, WORD cbData)
{
if (!Kitl.pfnEncode (pbFrame, cbData)) {
KITLOutputDebugString ("!KitlSendFrame: transport failed to encode the data frame\r\n");
return FALSE;
}
return KitlSendRawData (pbFrame, (USHORT) (cbData + Kitl.FrmHdrSize + Kitl.FrmTlrSize));
}
这个函数首先调用KitlEtherEncodeUDP对数据帧进行编码为UDP协议需要的格式。然后调用 KitlSendRawData将数据送出至PC.
BOOL KitlSendRawData (LPBYTE pbData, WORD wLength)
{
BOOL fRet;
if (!(KITLGlobalState & KITL_ST_MULTITHREADED) || InSysCall())
fRet = Kitl.pfnSend (pbData, wLength);
else if (IsDesktopDbgrExist ())
fRet = KCall((PKFN) Kitl.pfnSend, pbData, wLength);
else {
EnterCriticalSection (&KITLKCallcs);
fRet = Kitl.pfnSend (pbData, wLength);
LeaveCriticalSection (&KITLKCallcs);
}
return fRet;
}
首先判定系统没有在调度中且当前代码在不可剥夺状态状态运行,通过EthSend调用OEMEthSendFrame将数据送出就完成了工作。另外两个分支与我们分析的程序流没有关系先放一下。
BOOL
OEMEthSendFrame(
BYTE *pData, // IN - Data buffer
DWORD dwLength) // IN - Length of buffer
{
int retries = 0;
while (retries++ < 4) {
if (!pfnEDbgSendFrame(pData, dwLength))
{
#ifdef IMGSHAREETH
ProcessVMiniSend();
#endif //IMGSHAREETH
return TRUE;
}
else
EdbgOutputDebugString("!OEMEthSendFrame failure, retry %u\n",retries);
}
return FALSE;
}
在发送数据帧的过程中专门有处理vMini发送的过程。由于kitl本身就不是很简单我们以后后面再用专门的文章说明vbridge的工作过程。完成了发送,我们继续下面的过程。
BOOL KITLPollResponse (BOOL fUseSysCalls, PFN_CHECK pfnCheck, PFN_TRANSMIT pfnTransmit, LPVOID pData)
{
DWORD dwLoopCnt = 0, dwLoopMax = MIN_POLL_ITER;
DWORD dwStartTime = CurMSec;
int nTimeMax = MIN_POLL_TIME; // start with 1 sec
BOOL fUseIter = FALSE, fUseTick = FALSE;
while (!pfnCheck (pData)) {
//
// if we've already connected with desktop, use the desktop
// "Retransmit" package to determine if we need to retransmit
//
if (!(KITLGlobalState & KITL_ST_DESKTOP_CONNECTED)) {
if (fUseTick) {
if ((int) (CurMSec - dwStartTime) > nTimeMax) {
// retransmit
if (!pfnTransmit (pData, fUseSysCalls))
return FALSE;
dwStartTime = CurMSec;
if (nTimeMax < MAX_POLL_TIME)
nTimeMax <<= 1;
}
} else if (fUseIter || (dwStartTime == CurMSec)) {
// if time isn't moving for a while, we'll
// use iteration.
if (dwLoopCnt ++ > dwLoopMax) {
if (!pfnTransmit (pData, fUseSysCalls))
return FALSE;
if (dwLoopMax < MAX_POLL_ITER)
dwLoopMax <<= 1;
dwLoopCnt = 0;
fUseIter = TRUE;
}
} else {
// time is moving, just use tick from here
fUseTick = TRUE;
}
}
if (!KITLPollData(fUseSysCalls, pfnTransmit, pData)) {
return FALSE;
}
}
return TRUE;
}
static BOOL TranCnxDsktp (LPVOID pParam, BOOL fUseSysCalls)
{
return KitlSendFrame (PpfsFmtBuf, (WORD) (DWORD) pParam);
}
这个函数的主体是一个循环,终止的条件是!pfnCheck (pData),这个函数是由前面传递进来的,返回值为KITLGlobalState & KITL_ST_DESKTOP_CONNECTED,也就是说在得到桌面连接之前是不会返回的也就是说启动kitl以后不与桌面计算机连接windowsCE的是无法启动的。由于从dwStartTime定义到dwStartTime == CurMSec的判定仅仅需要很少的时间就可以完成这段时间内CurMSec是不会被改写的就可以通过循环进行超时检查
并通过TranCnxDsktp重新发送数据,直到KITLPollData设置KITLGlobalState。
static BOOL KITLPollData(BOOL fUseSysCalls, PFN_TRANSMIT pfnTransmit, LPVOID pData)
{
LPBYTE pRecvBuf = PollRecvBuf;
if (fUseSysCalls && (KITLGlobalState & KITL_ST_MULTITHREADED)
&& !(pRecvBuf = _alloca(KITL_MTU))) {
KITLOutputDebugString("!KITLPollData: STACK OVERFLOW!\r\n");
return FALSE;
}
HandleRecvInterrupt(pRecvBuf, fUseSysCalls, pfnTransmit, pData);
return TRUE;
}
由于我们上面传来的fUseSysCalls参数为false所以前一段检查操作并不进行。直接调用 HandleRecvInterrupt。
void HandleRecvInterrupt(UCHAR *pRecvBuf, BOOL fUseSysCalls, PFN_TRANSMIT pfnTransmit, LPVOID pData)
{
WORD wLen = KITL_MTU;
BOOL fFrameRecvd;
// Receive data into buffer
do {
if (!fUseSysCalls)
fFrameRecvd = Kitl.pfnRecv (pRecvBuf, &wLen);
else if (IsDesktopDbgrExist ())
fFrameRecvd = KCall((PKFN) Kitl.pfnRecv, pRecvBuf, &wLen);
else {
EnterCriticalSection (&KITLKCallcs);
fFrameRecvd = Kitl.pfnRecv (pRecvBuf, &wLen);
LeaveCriticalSection (&KITLKCallcs);
}
if (fFrameRecvd) {
ProcessRecvFrame (pRecvBuf,wLen,fUseSysCalls, pfnTransmit, pData);
wLen = KITL_MTU;
}
} while (fFrameRecvd);
}
通过Kitl.pfnRecv调用pfnEDbgGetFrame指向的CS8900DBG_GetFrame读取当前的数据帧送交ProcessRecvFrame处理。注意,这儿的pfnEDbgGetFrame并不是通过DMA或者是网卡传来的中断启动的而是使用查询的方法进行的,这就是为什么这儿并没有启动中断仍然能够使用以太网卡进行数据传输数据的原因。随后,我们将计算机端送来的数据送交ProcessRecvFrame处理。
static BOOL ProcessRecvFrame(UCHAR *pFrame, WORD wMsgLen, BOOL fUseSysCalls, PFN_TRANSMIT pfnTransmit, LPVOID pData)
{