5.1 概述
本章将通过AM79C97X网络通信芯片的例子来说明网卡的驱动原理。该通信芯片不仅具有网络通信功能,而且还建有PCI接口控制管理功能。在操作系统通过对AM79C97X的PCI接口的自动配置为该设备分配内存资源和IO资源,这些内存资源和IO资源将是CPU控制AM79C97X网络通信芯片的主要的主要通道。
表4.1列出了AM79C97X芯片的PCI配置寄存器。在配置寄存器的10h地址记录了该PCI设备对IO资源的要求:32字节;在配置寄存器的14h地址记录了该PCI设备对内存资源的需求:32字节。其实这两个地址所映射的内容是一样的,即CPU控制AM79C97X芯片的主要接口。一般来说既有IO接口又有内存接口的情况下,首先选用内存接口,因为内存接口访问速度要远高于IO接口。
表5.1列出了映射到内存空间的32字节所对应的含义。
偏移 | 字节数 | 寄存器 |
00h~0Fh | 16 | APROM |
10h | 2 | RDP |
12h | 2 | RAP |
14h | 2 | Reset Register |
16h | 2 | BDP |
18-1Fh | 8 | 保留 |
其中APROM是AM79C97X芯片外接一个串行的EEPROM,在H_RESET信号之后,EEPROM的内容会自动读入到AM79C97X芯片的相应位置,其中头16字节会自动读入APROM空间。表5.2列出了APROM空间的数据结构。
表格 5.2 APROM空间的数据结构
偏移 | 字节数 | 寄存器 |
00h~05h | 6 | MAC地址 |
06h~0Bh | 6 | 保留 |
0Ch~0Dh | 2 | checksum |
0Eh | 1 | 'W' |
0Fh | 1 | 'W' |
RAP为寄存器地址端口。在AM79C97X芯片中除了配置寄存器之外,还有两类寄存器:CSR(配置和状态寄存器)、BSR(总线状态寄存器)。要访问CSR寄存器时,首先在RAP寄存器中写入CSR寄存器的地址,然后直接访问RDP就完成了相应CSR寄存器的访问。BSR的访问原理相同。其中RDP用于控制AM79C97X芯片以及状态的获取,BDP用于配置总线接口单元以及LED指示灯的控制。关于BSR和CSR寄存器的详细内容可以参考AM79C97X的数据手册。
复位寄存器:对复位寄存器的读操作将激发AM79C97X芯片产生一个软件复位操作。
5.2 AM79C97X网络驱动程序的结构
网络驱动程序有一个非常复杂结构,里面包含了各种各样的网络协议。就目前最为常用的internet网络协议来说就包含了应用层、传输层、网络层、链路层以及物理层等5个层次,每个层次必须遵守各自的协议。就AM79C97X通信芯片来说,与之相关的主要是链路层,它负责为网络层提供服务。因此本章研究的重点在于链路层的驱动。
Internet网络协议的一个明显的特征,就是它的各个层次是严格分明的,链路层通过通用的函数接口为网络层提供服务。图5.1则说明了链路层驱动函数库的层次关系。
图5.1 链路层驱动的层次关系
其中函数库muxLib和函数库endLib与硬件无关,主要实现了初始化过程以及一些链路层协议,而函数库ln97xEnd主要用于AM79C97X通信芯片的控制。网络层和链路层之间虽然在操作上层次比较分明,但是其内部的数据结构比较复杂,因此本章会在最后简单的介绍一个ioProto函数库,说明网络层和链路层在数据结构上的关联。
5.2.1 函数库muxLib
函数库muxLib为网络层提供了一组链路层的通用接口,用于处理数据链路层和网络层的通信从而将网络层和链路层隔离开来。muxLib函数库在网络驱动中的位置如图5.1所示。
该函数库主要提供了两个功能:一是对于系统中所有网卡的管理,主要的核心在于endList链的管理;另一个在于对每个END,管理其网络服务链即END_OBJ.protocols,这也就是为什么该函数库多采用数据结构MUX_BIND_ENTRY作为各个函数处理中的主要参数,该结构包含一个END设备和一个网络服务:
typedef struct muxBindEntry
{
int unitNo; /* 设备的unit号 */
char devName[END_NAME_MAX]; /* 设备的名称 */
long netSvcType; /* 网络服务类型 */
long netDrvType; /* 网络驱动类型 */
UINT32 flags; /* BIB 入口标识 */
END_OBJ* pEnd; /* END对象指针 */
void* pNetCallbackId; /* 网络服务回调Id */
FUNCPTR addrResFunc; /* 地址解析函数指针 */
FUNCPTR netStackRcvRtn; /* network service stackRcvRtn 指针 */
}MUX_BIND_ENTRY, *MUX_ID;
vxWorks系统中通常会遇到一个缩写END,END的全称是Enhanced Network Drivers,即增强型网络驱动,它的作用主要是为了处理链路层的一些协议,并不涉及具体的网络接口设备的控制。
在研究函数库endLib之前还需要理解另外一个术语:NPT。NPT的全称是Network Protocol ToolKit,网络协议工具组件,它在保留END全部功能的基础上进一步的扩展。它们的不同之处它们处理链路层帧结构的时候有所不同:对于END类型的网络设备,在网络层调用muxLib库函数发送数据之前,所有的链路层数据包都已经封装好了,而对于NPT类型的网络设备,网络层在调用MUXtk层时还没有形成完整的数据包,需要在MUXtk根据目标IP地址组织完整的数据包。参见图5.2的结构。
图5.2 使用END和NPT发送网络层报文的区别
同样在接收数据时,END和NPT各有如下图*所示不同的操作。
图5.3 使用END和NPT接收网络报文的区别
如果一个网络设备被定义成一个END类型那么就不能被定义为一个NPT类型,反之亦然。无论END类型还是NPT类型,它们的都采用END_OBJ的数据结构。由于么全END类型比较完整,条理也比较清晰,因此在后面的分析中我们将更多的精力放在END类型的设备中,对mux层的分析也仅限于函数库muxLib而不再分析muxTkLib。
由于在网络驱动中轮询模式实际上很少使用,本章将不再对轮询模式的相关函数进行分析。需要注意的是这个函数库中的一些轮询函数还存在这不少的bug,完全没有办法正常执行。一个最直观的例子就是函数muxPollStart中分配并初始化了ppMuxPollDevices及pMuxPollMblkRing数组之后就马上启动muxPollTask进行,这时候ppMuxPollDevices及pMuxPollMblkRing数组中的元素都是NULL,muxPollTask函数可以对数值为NULL的pMuxPollMblkRing数组元素进行重新分配,可是对ppMuxPollDevices就没有判断直接使用,这样就造成了内存的泄露。这是一个十分严重的错误。如果需要使用到轮询模式,那么这一点尤其需要注意。
1. STATUS muxLibInit (void)
主要用于初始化与muxLib库相关的一些数据结构,比如muxLibState.lock及endlist等等。最终所有的end设备经处于stop状态。
2. void * muxDevLoad
(
int unit,
END_OBJ * (*endLoad) (char*, void*),
char * pInitString,
BOOL loaning,
void * pBSP
)
函数muxDevLoad用于装载网卡驱动。该过程主要分几步:第一步,调用特定网卡的驱动函数(*endLoad),如果endDevTbl数组中正好试探到了am97x的驱动,那么endLoad函数指定的函数为config/pcpentium/sysLn97xEndLoad,参数为空白字符串(字符串地址存在,但长度为0),深入研究一下sysLn97xEndLoad,sysLn97xEndLoad函数发现为空白字符串,则调用ln97xEnd.c/ln97xEndLoad (pParamStr),ln97xEndLoad发现参数为空字符串,自动将字符串"lnPci"复制到参数中,并返回到muxDevLoad;第二步,调用函数endFindByName在endList链表中查找是否存在"lnPci"且unit号与pDevTbl->unit相同这个设备,如果有的话说明已经安装过了,否则需要在endList添加这个设备结构END_TBL_ROW,其名字为"lnPci";第三步,以字符串参数paramStr调用函数sysLn97xEndLoad试探性的load。一旦成功,就将其挂载到endlist中。
关于该函数的执行过程,还会在后面关于网卡驱动的动态安装过程分析中做进一步详细分析。
注意endList的结构如图5.4所示。注意在计算机系统中可能会有多块网卡,这些网卡的类型也并不一定相同,endList记录了系统中网卡的的情况,同一类型的网卡形成一个END_OBJ结构链表,而不同类型的网卡分属于不同的链,这些链又通过END_TBL_ROW结构链在一起。
当装载一个网卡驱动时,首先要检查该类型及标号为unit的网卡有没有装过,如果装过则不需要安装,直接退出;否则还要明确是该类型的网卡有没有安装,如果没有安装则需要重新分配一个END_TBL_ROW结构变量并链接到endList链中,如果该类型的网卡驱动已经装过,只是没有安装标号为unit的网卡驱动,那么只需要分配一个END_OBJ结构挂在相应的END_TBL_ROW结构变量为链头的链上即可。
图5.4 endList变量的数据结构图
3. STATUS muxDevStart
(
void * pCookie
)
打开网卡控制芯片的中断,启动网络设备开始工作。注意在运行muxDevLoad函数完成网卡驱动装载之后,系统将会建立一个如图5.5所示的数据结构,这个数据结构的net_funcs结构指针变量pFuncTable已经赋了正确的数值,因此只需要调用函数表pFuncTable中的相应函数即可以完成相应的硬件操作。
图5.5 数据结构END_OBJ中的pFuncTable指针
4. STATUS muxDevStop
(
void * pCookie
)
直接调用函数pEnd->pFuncTable->stop停止网卡设备(参见图5.3)。
5. void muxShow
(
char * pDevName,
int unit
)
显示网卡的工作状态,主要包括:网卡的工作模式(轮询/中断)、设备的名称和编号以及该设备所支持的网络协议等等。
6. void * muxBind
(
char * pName,
int unit,
BOOL (*stackRcvRtn) (void*, long, M_BLK_ID, LL_HDR_INFO *, void*),
STATUS (*stackShutdownRtn) (void*, void*),
STATUS (*stackTxRestartRtn) (void*, void*),
void (*stackErrorRtn) (END_OBJ*, END_ERR*, void*),
long type,
char * pProtoName,
void * pSpare
)
在网卡驱动上绑定一个网络服务,即在其END_OBJ结构的protocols链中添加一个NET_PROTOCOL结构,如图5.4。
在添加NET_PROTOCOL结构时,不同的网络服务类型有不同的要求,具体来说有如下要求:
- l MUX_PROTO_SNARF类型的网络服务结构总是保存在protocols链表的头部位置,pEnd->snarfCount记录了MUX_PROTO_SNARF类型的网络服务的数量;
- l END_OBJ结构的protocols链中除MUX_PROTO_SNARF、MUX_PROTO_PROMISC之外,其他类型的网络服务每种最多只能有一个;
- l 所有类型的网络服务都要指定一个stackRcvRtn函数;
- l 除MUX_PROTO_OUTPUT以外,其它类型的网络服务都要指定stackShutdownRtn、stackTxRestartRtn、stackErrorRtn函数;
- l 所有绑定网络服务结构的nptFlag元素为FALSE,表明是MUX类型;
- l 除MUX_PROTO_OUTPUT网络服务以外都要指定服务的名称,如果参数pProName指定了名称,就采用指定的名称,否则名称为Protocol %d,这里%d为协议的编号;
- l 如果新绑定的网络服务是MUX_PROTO_OUTPUT以及MUX_PROTO_PROMISC类型的网络服务结构,则直接存放在protocols链的尾部;否则要存放在protocols链中的MUX_PROTO_SNARF类型的网络服务结构之后的第一个位置;
参数说明:
char * pName:网卡的名字如ln、ei等等;
int unit:某一类网卡的序号,和pName一起确定一个END设备;
BOOL (*stackRcvRtn) (void*, long, M_BLK_ID, LL_HDR_INFO *, void*):回调函数,MUX可以在收到数据包时调用该函数;
STATUS (*stackShutdownRtn) (void*, void*):回调函数,MUX可以通过这个函数关闭网络服务;
STATUS (*stackTxRestartRtn) (void*, void*):回调函数,MUX可以通过这个函数通知网络服务可以发送报文;
void (*stackErrorRtn) (END_OBJ*, END_ERR*, void*):回调函数,MUX可以通过这个函数向网络服务报告错误;
long type:RFC1700规定的网络服务类型。如0x800表示IP协议;
char * pProtoName:协议名称;
void * pSpare:指向网络服务的数据区域,MUX调用回调函数时可用于传递参数。
图5.6 END_OBJ结构中的protocols链
7. STATUS muxSend
(
void * pCookie,
M_BLK_ID pNBuff
)
调用函数pEnd->pFuncTable->send (pEnd, pNBuff)发送一个数据包。
注意:
- l 调用muxSend函数前要确保数据包pNBuff中已经包含了链路层报文头。
- l END_OBJ.mib2Tbl.ifOutNUcastPkts中记录了发送的广播和组播报文的总数据包数量;
- l MUX_PROTO_OUTPUT类型的网络服务的主要任务就是监控所有发送的报文,因此如果网络驱动支持MUX_PROTO_OUTPUT网络服务,则需要调用回调函数pEnd->outputFilter->stackRcvRtn进行处理。
8. STATUS muxReceive
(
void * pCookie,
M_BLK_ID pMblk
)
调用protocols链中的各个NET_PROTOCOL.stackRcvRtn函数将收到的数据包交给网络服务层进行处理。
从这个函数中也可以看出几个方面的内容:
- l MUX_PROTO_SNARF及MUX_PROTO_PROMISC类型的网络服务可以对所有收到的数据包进行处理;
- l MUX_PROTO_OUTPUT类型的网络服务不处理所有接收的数据包。
- l 对于标准类型的(非MUX_PROTO_SNARF、MUX_PROTO_PROMISC及MUX_PROTO_OUTPUT类型)网络服务只能处理与之类型相同的数据包;
- l 函数muxReceive将收到的数据包通过回调函数stackRcvRtn交给所有的网络服务进行处理,如果某一网络服务需要对数据包进行处理,那么回调函数将返回TRUE,否则返回FALSE。
- l END收到一个数据包,将根据protocols链的顺序交给各个数据包进行处理,如果由其中的任何一个网络服务处理后将直接返回,因此后面的网络服务不再处理,处理的优先级从高到底依次为:MUX_PROTO_SNARF、标准型、MUX_PROTO_PROMISC类型。当然从muxBind函数中END_OBJ.protocols链的存放顺序也可以看出其存放顺序:MUX_PROTO_SNARF存放在链表前端,MUX_PROTO_PROMISC及MUX_PROTO_OUTPUT存放在链表尾端。
- l END_OBJ.pMib2Tbl中存放的是收发报文的统计。
9. STATUS muxIoctl
(
void * pCookie,
int cmd,
caddr_t data
)
这个函数提供了一个通用的控制接口,通过该通用的控制接口可以完成如下功能:
- EIOCMULTIADD:增加一个组播地址;
- EIOCMULTIDEL:删除一个组播地址;
- EIOCMULTIGET:获取该设备所有的组播地址;
- EIOCGMCASTLIST:获取该设备的组播地址链表;
- EIOCSMIB2233:修改mib2Tbl;
- EIOCSFLAG及其他命令需要交给硬件底层处理函数进行处理,即调用函数pEnd->pFuncTable->ioctl进行处理;
注意:如果修改了END设备的flag,那么需要通知所有其他网络服务该设备的flags已经改变。
10. STATUS muxMCastAddrAdd
(
void * pCookie,
char * pAddress
)
调用函数pEnd->pFuncTable->mCastAddrAdd (pEnd, pAddress)增加一个组播地址。参见图5.3。
11. STATUS muxMCastAddrDel
(
void * pCookie, /* binding instance from muxBind() or muxTkBind() */
Char * pAddress /* Address to delete from the table. */
)
直接调用函数pEnd->pFuncTable->mCastAddrDel (pEnd, pAddress)。参见图5.3。
12. int muxMCastAddrGet
(
void * pCookie,
MULTI_TABLE * pTable
)
直接调用函数pEnd->pFuncTable->mCastAddrGet (pEnd, pAddress)。参见图5.3。
13. STATUS muxUnbind
(
void * pCookie,
long type,
FUNCPTR stackRcvRtn
)
解除掉一个协议绑定。pCookie指向一个MUX_BIND_ENTRY结构,它中间包含一个END设备和一个网络服务。
14. STATUS muxDevUnload
(
char * pName,
int unit
)
卸载一个END设备。参考前面图5.2的结构,卸载的时候要分为一下几个步骤:
- l 首先找到给END设备;
- l 调用END_OBJ.protocols链中的所有stackShutdownRtn函数进行卸载,注意对MUX和NPT类型的不同,其卸载函数的调用也有所不同;
- l 如果一种类型的END设备全都卸载,那么要删除endList中对应类型的END_OBJ结构,参见图5.2;
- l 调用函数pEnd->pFuncTable->unload进行硬件底层的unload;
- l 释放分配的END_OBJ结构的内存。
15. M_BLK_ID muxLinkHeaderCreate
(
void * pCookie,
M_BLK_ID pPacket,
M_BLK_ID pSrcAddr,
M_BLK_ID pDstAddr,
BOOL bcastFlag
)
调用函数pEnd->pFuncTable->formAddress将源地址、目的地址、类型等信息形成报文头插入到pMblk的数据头部。
注意:输入参数pDstAddr.mBlkHdr.reserved保存的是报文类型。
16. M_BLK_ID muxAddressForm
(
void * pCookie,
M_BLK_ID pMblk,
M_BLK_ID pSrcAddr,
M_BLK_ID pDstAddr
)
调用函数pEnd->pFuncTable->formAddress将源地址、目的地址、类型等信息形成报文头插入到pMblk的数据头部。
注意:该函数与函数muxLinkHeaderCreate的不同之处在于前者不支持组播地址而后者支持。
17. STATUS muxPacketDataGet
(
void * pCookie,
M_BLK_ID pMblk,
LL_HDR_INFO * pLinkHdrInfo
)
调用函数pEnd->pFuncTable->packetDataGet(pMblk, pLinkHdrInfo)从一个数据包中获取其数据信息,将获取的信息存放在pLinkHdrInfo指向的LL_HDR_INFO结构中。
18. STATUS muxPacketAddrGet
(
void * pCookie,
M_BLK_ID pMblk,
M_BLK_ID pSrcAddr,
M_BLK_ID pDstAddr,
M_BLK_ID pESrcAddr,
M_BLK_ID pEDstAddr
)
从一个数据包中获取其地址信息。通过调用函数pEnd->pFuncTable->addrGet (pMblk, pSrcAddr, pDstAddr,pESrcAddr, pEDstAddr)直接实现。
参数说明:
- void * pCookie:一个MUX_BIND_ENTRY结构中间包含一个ENDshebei和一个网络服务;
- M_BLK_ID pMblk:M_BLK结构指针,其中存放了数据;
- M_BLK_ID pSrcAddr:M_BLK结构指针,其中存放了源地址;
- M_BLK_ID pDstAddr:M_BLK结构指针,其中存放了目的地址;
- M_BLK_ID pESrcAddr:M_BLK结构指针,其中存放了源地址;
- M_BLK_ID pEDstAddr:M_BLK结构指针,其中存放了目的地址。
19. END_OBJ * endFindByName
(
char * pName,
int unit
)
查找endList链表,检查一个名称为pName,序号为unit的设备是否存在,如果存在返回其END_OBJ结构指针,否则返回NULL。
20. BOOL muxDevExists
(
char * pName,
int unit
)
查找endList链表,检查一个名称为pName,序号为unit的设备是否存在。
21. void muxTxRestart
(
void * pCookie
)
通知一个END设备绑定的的所有网络服务可以重新开始发送数据了。
对于protools链表中的每个协议结构,其中都会有一个stackTxRestartRtn回调函数用于通知网络服务可以重新开始发送数据而该函数的任务就是挨个调用protools链表中的每个协议NET_PROTOCOL结构中的stackTxRestartRtn回调函数。
22. void muxError
(
void * pCookie,
END_ERR * pError
)
对END_OBJ结构协议链protocols中每个协议结构NET_PROTOCOL,调用其中的回调函数stackErrorRtn通知各自的网络服务出现了错误。
23. STATUS muxAddrResFuncAdd
(
long ifType,
long protocol,
FUNCPTR addrResFunc
)
muxAddrResFuncAdd、muxAddrResFuncGet、muxAddrResFuncDel这三个函数的主要作用是对LIST addrResList[MUX_MAX_IFTYPE + 1]的管理。addrResList是一个数组,它中间存放了MUX_MAX_IFTYPE个链表(MUX_IFTYPE是从1开始的,addrResList[0]未使用),元素addrResList[ifType]是一个链头,该链中保存的是IFT_xxx对应的地址解析协议函数,该解析函数能够将一个网络服务的地址翻译成一个链路层协议地址。一个常见的例子就是arpresolve函数,它能够将一个IP地址翻译成一个链路层地址。
muxAddrResFuncAdd函数就是在链addrResList[ifType]中增加一个MUX_ADDR_REC结构
typedef struct mux_addr_rec
{
NODE node;
long protocol;
FUNCPTR addrResFunc;
} MUX_ADDR_REC
24. FUNCPTR muxAddrResFuncGet
(
long ifType,
long protocol
)
参见函数muxAddrResFuncAdd。
25. STATUS muxAddrResFuncDel
(
long ifType,
long protocol
)
参见函数muxAddrResFuncAdd。
26. STATUS muxDevStopAll
(
int timeout
)
对endList链上的每个END,调用pEnd->pFuncTable->stop (pEnd)函数停止其驱动程序工作。
27. LOCAL STATUS muxDevStopAllImmediate (void)
以参数0调用函数muxDevStopAll,即获取信号量时muxLibState.lock无需等待。
28. LOCAL void muxEndFlagsNotify
(
void * pCookie,
long endFlags
)
该函数在tNetTask的上下文中执行,其目的是通知一个END中的所有网络服务该设备的flag发生了改变(最初更改该flag的网络服务则无需通知)。
注意:调用函数随着mux和npt类型的不同而有所差异。如果是mux类型,直接调用函数pProto->stackErrorRtn (pCookie, pError, pProto->pSpare);如果是NPT,如果pProto->pSpare存在,则pProto->pSpare指向一个MUX_BIND_ENTRY结构,回调的参数保存在该MUX_BIND_ENTRY结构的pNetCallbackId元素中。
29. STATUS muxDevStartAll
(
)
遍历endList链表中所有END_OBJ结构,并对每个END_OBJ结构其执行函数pEnd->pFuncTable->start(pEnd->devObject.pDevice)开始驱动程序。
30. STATUS muxIterateByName
(
char * pName,
FUNCPTR pCallbackRtn,
void * pCallbackArg
)
这个函数是对endList链中所有名字为pName的END,执行(*pCallbackRtn) (pCallbackArg, (void *)pEnd,pEnd->devObject.unit)函数。
31. void * muxProtoPrivDataGet
(
END_OBJ * pEnd,
int proto
)
遍历一个END的协议链,查找出其对应的协议数据区域(元素NET_PROTOCOL.pSpare)。参考结构NET_PROTOCOL。