起来真是雷人,最近几天纠结与一个最基本的概念,就是NDIS与WinSock关系,想来想去都没有想明白,真实汗Ing,赶紧找了篇精美的文章来扫盲一下。
原文如下:
文章转自http://www.cnblogs.com/sankye/articles/1651280.html
1.WinCE的网络通信架构
WinCE的网络通信架构如图1所示,WinCE的网络通信架构中一个重要的角色是网络结构规范(NetworkDriver Interface Specification,NDIS),它支持多种网络媒体,以及提供包括TCP/IP等多种网络协议。
图1 WINCE通信网络的层
其中最上层的Wins0ck是提供给应用层的接口,一般开发网络应用都会用Winsock接口来开发。NDIS位于协议驱动层下面,而位于硬件驱动Miniport Driver之上。协议驱动层通过调用NDIS封装层的接口函数,实现与底层硬件驱动的交互。对于协议层来说,NDIS相当于一个Miniport Driver,而对于底层的硬件驱动来说,NDIS相当于上层的协议层,所以NDIS起到承上启下的作用,也起到对底层硬件接口的规范作用。
2.WinCE网络驱动架构和实现原理
在WinCE中,网卡驱动的实现原理如图2所示,在上层的协议驱动层看来,它调用NDIS接口函数访问网络设备,其具体实现过程(如图2虚线框),是通过调用底层的Miniport Driver接口函数来实现。在WinCE系统中NDIS接口函数库是Microsoft开发好的,所以开发winCE下的网卡驱动就是编写一个Miniport Driver,它向上导出接口函数与NDIS接口实现对接,向下直接管理网卡硬件。
图2 WINCE网络驱动原理
3.网络驱动的编程与实现
3.1网络驱动接口的实现流程
在WinCE中,应用层通过调用NDIS接口(图3实线框)实现与底层硬件的交互,而NDIS接口是微软已经开发好的,被定义成一个数据结构体的形式。开发网卡驱动就是写一个Miniport Driver,导出相应的Miniport接口函数(图3的虚线框),这些接口函数会在系统注册一个Miniport Driver的时候与NDIS封装层的接口函数对接,这样内核协议层通过调用NDIS的接口就可以访问底层硬件。
微软定义的与Miniport Driver相关的NDIS标准接口总共有18个,针对不同的网络设备其接口的实现也不尽相同,本流程图中罗列出的是CS89OO网卡驱动所实现的接口函数,具体的接口实现可参照Platform Builder的帮助文档。
3.2网络驱动接口的具体实现
实际网络驱动的编写,就是理解wincE下网络驱动程序的构架,然后针对实际的硬件编写代码,实现相应的中间层Miniport Driver接口函数。下面结合利用WinCE5.0内核在脉冲发生器嵌入式主板上移植编写嵌入式CS8900网卡驱动程序的实例,介绍网卡驱动程序Miniport Driver接口的具体实现(由于本驱动的硬件设备是CS8900,所以在函数接口的取名上一律用CS8900代替Miniport Driver)。
3.2.1网络驱动程序的入口函数
DriverEntry,该函数中首先调用NdisMinitializeWrapper函数来通知NDIS Library要注册一个Miniport。然后初始化MINIPORT结构体,所有的Miniport的相关接口函数都会赋到 MINIPORT 结构中, 最后调用NdisMRegisterMiniport来注册Miniport。通过此函数,实现了Miniport Driver接口与NDIS接口的对接。
3.2.2网络设备的初始化接口
Miniportlnitialize, 该函数为调用函数CS8900RegisterAdapter来完成网络设备的初始化,而CS8900RegisterAdapter 又会调用CS8900Initialize,CS8900Initialize函数会相继调用:findCS,查找网络设备;resetCS,重启网络控制器,并设置工作模式为16bit的I/0模式;InitIrq,开启网络控制器的中断;initCS,设置临时的物理地址,为网络控制器设置与嵌入式芯片之间中断的硬件连接,以及总线读写的时序。
3.2.3网络数据包的发送
WinCE网络数据发送的流程:当上层协议驱动要发数据时,调用NdisSend请求NDIS发送数据包,NDIS将会调用紧接其下的中间层驱动的CS8900Send,该函数首先调用NdisQueryPacket,得到需要发送包的数据信息,并拷贝到一个缓冲区暂存,这样做的目的是保证包数据不被丢失。然后调用CS8900RequestTransmit,向网络控制器发送传送数据的请求,最后调用函数CS8900CopyTxFrame完成数据包的发生。
3.2.4网络数据的接收和中断
网络设备的接收数据包时通过中断实现,当网络接口接收到新数据包时,发送完成或者报错误信息及连接状态都会出发中断,通常中断处理程序通过检测硬件状态寄存器判断是哪种情况。
当网络设备有数据到来的时候,将触发中断,相应的中断处理程序接管中断后,将调用Miniport Driver所注册的中断处理例程CS8900Isr,通过读取CS8900的中断寄存器判断是否是接收到数据中断,如果是就调用数据接收函数CS8900ReceiveEvent。Miniport Driver通常在这里把网卡上的数据拷贝到Miniport Driver缓冲区队列中去,出于效率的考虑,Miniport Driver这时可能不会立即通知上层处理新的数据,因为很可能,马上还有随后的新的数据到来,当接收到的包的数量达到一定程度的时候,驱动程序的接收线程会调用函数NdisMIndicateReceivePacket指示新的NDIS新数据的到来。
3.2.5Miniport Driver其他接口
CS8900Reset,复位硬件网卡;
CS8900Querylnformation,网卡信息查询函数;
CS8900Setlnformation,设置网卡信息函数。
3.2.6 驱动下实现的CS8900A的几个函数
1.读写ReadPacketPage和WritePacketPage。这两个函数通常并不被EthDbg驱动程序的使用者直接调用。而是这个两个函数向其他EthDbg驱动程序接口函数提供了最基本的读写CS8900A的PacketPage内部的控制与状态寄存器和收发缓冲区的功能。
在源文件%_WINCEROOT%/PLATFORM/COMMON/SRC/COMMON/ETHDRV/CS8900A/cs8900a.c中定义了一个静态全局指针变量g_pCS8900:
STATIC CS8900A_REGS *g_pCS8900;
其类型CS8900A_ERGSS是在同一源文件中定义的结构体:
Typedef struct{
Unsigned _int16 DATA0;
Unsigned _int16 DATA1;
Unsigned _int16 TXCMD;
Unsigned _int16 TXLENGTH;
Unsigned _int16 ISQ;
Unsigned _int16 PAGEIX;
Unsigned _int16 PAGE0;
Unsigned _int16 PAGE1;
}CS8900A_REGS;
显然,静态全局指针变量g_Pcs8900所指向的CS8900A_REGSS结构体数据专门用于映射CS8900A的8个I/O端口,g_Pcs8900指针的实际取值就应该是这8个I/O端口的基地址。ReadPacketPage和WritePacketPage两个函数的实现对CS8900A的PacketPage读写就是通过这个g_Pcs8900指针进行的。
2.硬件初始化函数CS8900AInit。CS8900AInit函数的功能是执行对CS8900A以太网控制器芯片的硬件初始化,并且设置其工作模式至一个确定的状态。CS8900AInit函数在%_WINCEROOT%/PLATFORM/DEVICEEMULATOR/SRC/DRIVERS/ETHERNET/cs8900a.c源文件中实现,其函数定义:
BOOL CS8900AInit(UINT *pAddress, UINT32 offset, UNIT16 MAC[3]);
*pAddress指针参数记录以太网控制器的I/O端口基地址。Offset成员则是偏移地址,在当前的CS8900AInit函数中它没有被使用。Mac数组记录以太网端口的48位MAC地址。
如果把对全局变量g_pCS8900赋值看作是CS8900AInit函数执行的第一步,则它的第二个执行步骤就是检测CS8900A以太网控制器芯片是否在目标硬件平台上真实存在:
If (ReadPacketPage(EISA_NUMBER)!=CS8900A_EISA_NUMBER)
{
OALMSGS(OAL_ERROR,(L”ERROR:CS8900AInit:Failed detect chip/r/n”));
Goto Exit;
}
检测CS8900A芯片是否存在的依据是读取PacketPage中便宜地址为0的产品ID寄存器,这是个只读的寄存器。如果读取EISA_NUMBER寄存器返回的16为数值是0X630E,则CS8900A芯片存在,否则不存在,CS8900AInit函数中止执行,并且向它的调用者返回FALSE表示执行失败。
CS8900AInit函数执行的第三个步骤就是通过软件操作出发CS8900A芯片复位:
WritePacketPage(SELT_CTL,SELF_CTL_RESET);
接下来,CS8900AInt函数接下来执行两个步骤:
1)等待CS8900A芯片软件复位后完成芯片初始化;
2)等待CS8900A芯片外置的用于存放芯片初始化配置信息的EEPROM存储器可被访问;
接下来CS8900AInit函数要为受CS8900A芯片控制的以太网端口设置MAC地址,方法是写INDIVIDUAL_ADDRESS寄存器:
WritePacketPage(INDIVIDUAL_ADDRESS + 0,mac[0]);
WritePacketPage(INDIVIDUAL_ADDRESS + 0,mac[1]);
WritePacketPage(INDIVIDUAL_ADDRESS + 0,mac[2]);
CS8900AInit剩下的代码就是配置CS8900A以太网控制器的收发数据帧的模式,分为以下四个步骤:
1)配置允许CS8900A以太网控制器芯片接收的以太网数据帧类型:
WritePacketPage(RX_CTL,RX_CTL_RX_OK|RX_CTL_INDIVIDUAL|RX_CTL_BRODCAST);
2)配置CS8900A以太网控制器芯片以中断方式接收数据帧:
WritePacketPage(RX_CFG,RX_CFG_RX_OK_IE);
3)配置CS8900A以太网控制器芯片选择使用第0号中断引脚;
WritePacketPage(INTERRUPT_NUMBER,0);
4)配置CS8900A以太网控制器芯片使之允许接收发送数据帧:
WritePacketPage(LINE_CTL,LINE_CTL_RX_OK|LINE_CTL_TX_ON);
3.发送以太网数据帧。CS8900ASendFrame函数的功能是向以太网发出一个数据帧。其函数原型:
UINT16 CS8900ASendFrame(UINT8 *pData, UINT32 length);
pData参数是指向待发送数据在主机中存放位置的地址指针,length参数记录是以字节为单位的待发送数据的长度。尽管函数的返回值类型定义为UINT16,但是它实际返回的是知识函数执行是否成功的BOOL值。
CS8900ASendFrame函数按照以下步骤完成数据的发送任务:
1)写发送命令和数据长度:
OUTPORT16(&g_pCS8900->TXCMD,TX_CMD_START_ALL);
OUTPORT16(&g_pCS8900->TXLENGTH,length);
这里的宏定义TX_CMD_START_ALL的实际数值(3<<6),它把发送命令端口TxCMD的第6位和第7位(TTxStart)置1,其作用是限定只有当整个数据帧都被写入CS8900A时才开始向外部网络发送数据。
2)检测CS8900A芯片已准备好接收来自主机的待发送数据:
Count = RETRY_COUNT;
While(count-->0){
If((ReadPacketPage(BUS_ST) & BUS_ST_TX_RDY)!=0) break;
}
If (count ==0) goto cleanup;
总线状态寄存器BusST(PacketPage内偏移地址为0138H)的第8位(Rdy4TxNOW)用于CS8900A芯片向主机通知已准备好接收数据,该位置1表示CS8900A已准备好接收来自主机的待发送数据。以上代码采用的是轮询而非中断的方式查询CS8900A芯片是否准备就绪,另一个更好的办法是,将缓冲配置寄存器BufCFG的第8位(Rdy4TxiE)置1,当CS8900A芯片准备好接收待发送数据时将向主机发出中断信号。
3)将待发送的数据依次写入CS8900A的数据端口:
Length = (length + 1)>>1;
While (length-- >0) {
OUTPORT16(&G_Pcs8900->DATA0, *(UINT16*)pData);
pData += sizeof(UINT16);
}
4.接收以太网数据帧CS8900AGetFrame函数。数据帧接收必定要涉及中断,还很有可能要使用DMA操作将接收到的数据从CS8900A芯片搬移到主机中。网络数据如果通过了CS8900A芯片的地址过滤器的筛选(单播地址或广播地址的数据帧),则CS8900A开始接收数据。如果一个数据帧被CS8900A全部接收完毕,具有有效的以太网数据帧长度并且CRC校验无错误,则CS8900A向主机触发RxOK中断,则主机通过CS8900A的数据端口将数据帧读出。
CS8900AGetFrame函数的原型定义如下:
UINT16 CS8900AGetFrame(UINT8 *pData, UINT16 *pLength);
pData参数是主机用来存放接收到的数据帧的内缓冲区起始地址,pLength参数所指向的内存单元记录接收缓冲区的以字节为单位的长度。CS8900AGetFrame函数的返回值是以字节为单位的实际接收到的数据的长度。
CS8900AGetFrame函数首先读CS8900A的ISQ端口,并且判断是否有RxOK中断存在:
Isq = INPORT16(&g_Pcs8900->ISQ);
If ((isq & ISQ_ID_MASK) == RX_EVENT_ID && (isq & RX_EVENT_RX_OK)!=0{
……
}
按照CS8900A的中断机制运行原理,当有中断事件发生时,除反映在事件寄存器的对应位外,还要把该事件寄存器关联到ISQ端口,然后触发中断引脚。此时,ISQ端口的最低6位记录所关联的寄存器的内部偏移地址,其余为是关联寄存器的数据内容。CS8900A芯片共有5个寄存器可以关联到它的ISQ端口,除3个时间寄存器(接收事件寄存器RxEvent、发送事件寄存器TxEvent和缓冲区事件寄存器BufEvent)外,另外两个是接收帧丢失计数器RxMISS和发送冲突计数器TxCOL。所以CS8900AGetFrame函数检测到RxOK中断必须满足两个条件:接收事件寄存器RxEvent(PacketPage内偏移地址为0124H)被关联到ISQ端口,并且其中的RxOK位(第8位)被置1.
如果这两个条件均满足,则主机可以从CS8900A的数据端口读取数据了。读取数据的操作可以按以下的顺序进行:首先是本次数据接收的状态和以字节为单位的数据总长度:
//Get RxStatus and length
Status = INPORT16(&G_Pcs8900->DATA0);
length = INPORT16(&G_Pcs8900->DATA0);
然后是由length指定字节数总长度的帧数据。
如果主机所提供的接收数据缓冲区不够用,则终止数据接收操作并且指令CS8900A丢弃接收到的数据:将接收配置寄存器RxCFG的第6位(Skip_1)置位。这会将当前存在于CS8900A芯片的接收数据缓冲区内的数据帧全部丢弃:
If (length> *pLength) {
// if packet doesn’t fit in buffer, skip it
Data = ReadPacketPage(RX_CFG);
WritePacketPage(RX_CFG, data |RX_CFG_SKIP_1);
Length = 0;
} else{
…
}
5.启用与禁用CS8900A的中断功能的函数CS8900AEnableInts和CS8900ADisableInts。在CS8900A中,总线控制器BusCTL(PacketPage内偏移地址为0116H)的第15位(EnableIRQ)是否被置位即表示CS8900A是否会根据相应的事件产生中断。
6.CS8900A的配置地址过滤机制的函数。CS8900ACurrentPacketFilter和CS8900AMulticastList。
4 网络驱动的编译与加载
在WinCE下,所有的驱动程序都以用户态的DLL文件形式存在。当编写完驱动的模块化接口之后,我们就要将这个模块编译成*.DLL的动态库,然后在编译系统的时候将该DLL动态库加载到系统内核里去,这样操作系统就可以在运行时动态的加载需要的应用程序。
下面以移植CS89O0网卡驱动为实例,介绍如何将网络设备驱动模块加载到WinCE 的内核,编译器为Platform-Builder5.O(简称PB5.O)。其编译、加载过程主要分为六步:
(1)在硬件平台BSP的DRIVERS目录下创建新目录CS89OO。
(2)修改DRIVERS下的DIRS文件,DIR下定义了编译器需要编译的内容,所以需要在DIRS文件中将CS8900目录添加上。
(3)将编写好网络驱动源程序代码拷贝到CS89OO目录下。
(4)编写网络设备驱动的sources文件,告诉编译器和连接器如何编译及连接本驱动程序。这样的sources文件在每个WinCE的驱动下面都有一个,是为了给PB编译驱动的时候提供编译“向导”的。其关键内容为:TARGETTYPE定义编译该驱动成为哪种形式,有DLL和Lib两种形式;TARGETLIBS定义编译该程序的时候需要连接的其他库;SOURCES确定需要编译的文件。本驱动被编译成的形式为DLL,链接了ndis,ntcompat,coredll,ceddk 4个静态库。
(5)编写CS8900的注册表文件,可参考WinCE自带的驱动源码ne2000网卡驱动的注册表文件编写CS8900的注册表文件。注册表定义了网卡的基本参数信息提供给操作系统,其中:Parms项提供网卡驱动在系统中的逻辑中断号,读写基地址,总线类型;TcpIp项提供了网卡IP
地址信息等,如果要修改IP地址就在本注册表项中修改。特别注意网卡的中断的设置,在WinCE中,与外设对应的中断在0AL层被定义,所以这里的中断号必须与0AL层设置的一致,否则网络驱动将无法工作。
(6)将驱动编译进系统内核,修改系统平台初始化文件platform.bib。
经过上述步骤之后,重新编译内核,将内核下载到嵌入式主板上,就可以看到类似Windows操作系统的网络连接一样标志,说明网卡驱动已经被加载到内核。
本文在介绍嵌入式WinCE网络驱动架构的基础上,参照WinCE提供的网络驱动模型,详细介绍了嵌入式WinCE以太网驱动程序的设计原理,并已经成功的移植了驱动程序,在嵌入式WinCE下稳定运行。本网络接口已经用于脉冲发生器的远程控制,运行稳定。