本文包含网络接口的硬件基本原理和VxWorks 6.6网络协议栈/驱动的分析,适用于VxWorks6.6以及之前的版本,并可用作其他版本协议栈的参考。
0. 概述
0.1 网络接口
以太网目前有两种模型,即ISO(国际标准化组织)定义的的OSI七层模型和IETF(互联网工程任务组)的TCP/IP五层模型,具体可参考 TCP/IP协议详解 。目前,TCP/IP为互联网的事实标准,具体分层如下所示:
TCP/IP MAC 又称为以太网控制器,既具有独立的链路层地址(MAC地址,又称为以太网地址),也就是通常意义上或者 狭义上的网口 ,用于控制链路层数据的传输; PHY 用于控制物理层传输,用于物理链路与MAC之间数据的编解码、物理链路控制、载波侦听、线序交换等功能,通常只包含PCS(物理编码子层)、PMD(物理介质相关子层)、PMA(物理介质附加子层)等,但是1000Mbps以上的高速链路需要更多的子层(DTE XGXS、PHY XGXS);0.2 约定
socket 即套接字,是BSD协议栈提出的网络接口,包含一个接收缓冲区,用于收发数据和控制数据传输; MBLK/Cluster/data 是BSD协议栈提出的缓冲区模型,每个数据区
data
对应一个
Cluster
,每个
Cluster
关联到1个
MBLK
上;
MBLK
作为以太网报文的代表,可以将多个以太网报文分片串成一个链表,用以提升数据收发效率;
1. END驱动架构
END驱动整体架构如下所示:
socket :由 sockLib 库提供,包括
socket/bind/connect/listen/accept/getsockopt/setsockopt/recv/recvfrom/recvmsg/send/sendmsg/sendto()
等;其中:
通用IO接口
:由
sockLib
库提供,包括
close/ioctl/write/ioctl()
;
ipcom/ipnet接口
:由
ipcom/ipnet
提供,包括
ifconfig
等;
sockLib
库位于
vxworks-6.6/target/src/wrn/coreip/sysdep/os/vxWorks/socket
,提供
socket接口
,并调用
iosDrvInstall()
接口安装IO设备驱动,从而为每个socket套接字创建描述符以提供
通用IO接口
,其中:
socket/accept
接口除了调用
ipcom/ipnet
注册的
socketRtn/acceptRtn()
钩子函数,还需要调用
iosLib
库接口创建IO设备作为socket描述符;
其余socket接口
在进行简单的参数检查后直接调用
ipcom/ipnet
注册的对应钩子函数;
通用IO接口
对应于
sockLib
库在安装IO设备驱动时注册的
socketClose/socketRead/socketWrite/socketIoctl()
接口,这些接口在进行简单的参数检查后直接调用
ipcom/ipnet
注册的对应钩子函数;
ipnet/ipcom
位于
components/ip_net2-6.6
,使用sockLibAdd()将AF_NET/AF_PACKET等协议栈及包含
socket/bind/connect/listen/accept/getsockopt/setsockopt/recv/recvfrom/recvmsg/send/sendmsg/sendto()
等钩子的功能列表注册到
sockLib
的sockLibMap[];其中:
ipcom
提供OS封装层,用于屏蔽OS的不同,并绑定到MUX层,主要代码位于
components/ip_net2-6.6、ipcom/port/vxworks
;
ipnet/ipcom
调用
muxLib
提供的
muxDevStart/muxDevStop/muxIoctl/muxSend/muxDevLoad/muxDevUnload/muxBind/muxUnbind()
接口来完成协议栈绑定、、驱动加载、网口控制和数据收发功能;
muxLib
位于
vxworks-6.6/target/src/wrn/coreip/common/mux
,用于将协议栈绑定到不同的网口上,并提供网卡控制接口,包括:
muxDevStart/muxDevStop/muxIoctl接口
:主要通过调用
endLib
封装的网口驱动功能列表中相应的
start/stop/ioctl
钩子来完成;
muxSend接口
:首先通过
endLib
封装的网口驱动功能列表中的
formAddress
钩子生成地址,然后调用
endLib
封装的网口驱动功能列表中的
packetDataGet
钩子和
muxBind
接口安装的
stackRcvRtn
来过滤本地数据包,最后调用
endLib
封装的网口驱动功能列表中的
send/formAddress/packetDataGet
钩子来完成发送功能;
muxReceive接口
:通过
muxBind
接口安装的
stackRcvRtn
将数据上传到
ipnet/ipcom
;
muxDevLoad接口
:调用驱动提供的
xxxEndLoad
接口加载网口驱动,然后调用
endLib
提供的
endFlagsSet
设置END_MIB_2233标志(
m5200FecEnd不支持2233 MIB
),并使用ioctl判断end类型以设置
pEnd->receiveRtn
为
muxReceive
(
m5200FecEnd类型为END_STYLE_END
);
end
层用于提供网卡驱动的封装结构和公用代码。
2. 网卡驱动全工作流程
2.1 初始化流程
网卡驱动初始化入口为usrNetworkInit():
usrNetworkInit :该位于
<工程目录>/prjConfig.c
中,调用
usrNetEndLibInit()
,用于初始化网卡驱动;
usrNetEndLibInit
:该接口位于
vxworks-6.6/target/src/config/usrNetwork.c
或
vxworks-6.6/target/config/comps/src/net/coreip/usrNetEndLib.c
中,关键流程如下所示:
vxbDevMethodRun() 遍历VxBus驱动,调用所有提供muxDevConnect接口的网卡驱动, 不适用 于lite5200b的Legacy网卡驱动; endDevTbl[] 数组包含Legacy网卡驱动,定义于BSP目录的configNet.h中:
#define FEC_LOAD_FUNC m5200FecEndLoad
#define FEC_LOAD_STR "-1:0x0:-1:-1:0x40:0x30:0x0:0xff:2:0x4:" \
FEC_CLOCK_SPEED(IPB_CLOCK_LITERAL)
#define FEC_BUFF_LOAN 1
END_TBL_ENTRY endDevTbl [] =
#ifdef INCLUDE_FEC_END
{ 0, FEC_LOAD_FUNC, FEC_LOAD_STR, FEC_BUFF_LOAN, NULL, FALSE},
#endif /* INCLUDE_FEC_END */
endPollStatsInit()用于初始化网口驱动的统计信息。
muxDevLoad():位于vxworks-6.6/target/src/wrn/coreip/common/mux/muxLib.c中,用于调用endDevTbl[]数组中Legacy网口驱动的初始化函数endLoad,关键流程如下所示:
muxDevStart():位于vxworks-6.6/target/src/wrn/coreip/common/mux/muxLib.c中,用于调用驱动注册的pEnd->pFuncTable->start()接口启动网口,并将网口设置为IFF_UP | IFF_RUNNING状态;
m5200FecEndLoad():驱动接口,主要功能是注册到endLib:
分配驱动信息结构和PHY信息结构;
调用m5200FecInitParse解析初始化字符串并保存在驱动信息结构中;
调用m5200FecInitMem申请临时发送缓冲区,并创建netpool网络缓冲池;
调用m5200FecSdmaTaskInit创建Bestcomm SDMA任务;
调用END_OBJ_INIT即endObjInit初始化END结构,注册网口驱动功能列表;
调用END_FLAGS_SET设置多播和广播标志;
m5200FecStart():驱动接口,主要功能是启动网口:
调用m5200FecReset复位以太网控制器;
调用m5200FecTbdInit/m5200FecRbdInit初始化收发缓冲区描述符环;
调用SYS_FEC_INT_CONNECT即intConnect挂接BestComm发送和接收任务中断以及通用中断处理函数;
调用SYS_FEC_INT_ENABLE即intEnable使能中断;
调用m5200FecPrePhyConfig初始化MAC地址、设置中断事件掩码、清除中断事件、配置内部MII控制器接口等;
调用m5200FecPhyPreInit根据驱动信息结构中的标志设置PHY信息结构中的工作模式标志;
调用_func_m5200FecPhyInit即m5200FecPhyInit初始化PHY,并根据PHY信息结构中的工作模式标志选择自协商模式或强制模式;
调用TaskIntClear清除Bsetcomm接收任务中断;
调用TaskStart启动Bsetcomm接收任务;
调用FEC_END_ETH_ENABLE使能以太网控制器;
调用END_FLAGS_SET将网口标志为IFF_UP | IFF_RUNNING,即工作状态;
调用netJobAdd将muxTxRestart添加到tNet0任务维护的netJobQueueId队列,进而调用绑定在网口上的协议栈的stackTxRestartRtn接口以复位协议栈。
2.2 发送流程
发送流程的入口为socket库提供的send/sendto/sendmsg()和ios子系统提供的write():
send/sendto/sendmsg():调用协议栈利用sockLibAdd()注册的SOCK_FUNC指针中的sendRtn/sendtoRtn/sendmsgRtn(),即ipcom_windnet_send/sendto/sendmsg()位于vxworks-6.6/target/src/wrn/coreip/sysdep/os/vxWorks/socket/sockLib.c;
write():调用socket库安装到ios子系统的socket驱设备的socketWrite()钩子,进而调用协议栈利用sockLibAdd()注册的SOCK_FUNC指针中的socketwriteRtn(),即ipcom_windnet_socketwrite(),位于vxworks-6.6/target/src/wrn/coreip/sysdep/os/vxWorks/socket/sockLib.c;
ipcom_windnet_send/sendto/sendmsg/socketwrite():调用ipcom_send/sendto/sendmsg/send(),位于components/ip_net2-6.6/ipnet2/src/ipnet_sock.c;
ipcom_send/sendto/sendmsg/send():调用ipcom_sendmsg(),位于components/ip_net2-6.6/ipnet2/src/ipnet_sock.c;
ipcom_sendmsg():调用sock->ops->send(),即ipnet_init()注册并由socket()接口使用ipcom_socket()安装的iptcp_send和ipnet_sock_udp_send接口;
iptcp_send和ipnet_sock_udp_send():
UDP分支:
ipnet_sock_udp_send():调用ops->i.network_send(),即ipnet_ip4_sendto(),位于components/ip_net2-6.6/ipnet2/src/ipnet_udp.c;
TCP分支:
iptcp_send():调用iptcp_create_output_seg(),位于components/ip_net2-6.6/ipnet2/src/iptcp.c;
iptcp_create_output_seg():调用iptcp_output(),位于components/ip_net2-6.6/ipnet2/src/iptcp.c;
iptcp_output():调用iptcp_sendto(),位于components/ip_net2-6.6/ipnet2/src/iptcp.c;
iptcp_sendto():调用sock->ops->network_send(),即ipnet_ip4_sendto(),位于components/ip_net2-6.6/ipnet2/src/iptcp.c;
7.ipnet_ip4_sendto():调用netif->link_ip4_output(),即ipnet_eth_if_init安装的ipnet_eth_ip4_output(),位于components/ip_net2-6.6/ipnet2/src/ipnet_ip4.c;
ipnet_eth_ip4_output():调用ipnet_if_output(),位于components/ip_net2-6.6/ipnet2/src/ipnet_eth.c;
ipnet_if_output():调用ipnet_if_drv_output(),位于components/ip_net2-6.6/ipnet2/src/ipnet_netif.c;
ipnet_if_drv_output():调用netif->ipcom.drv_output(),即ipcom_drv_eth_init安装的ipcom_drv_eth_output(),位于components/ip_net2-6.6/ipnet2/src/ipnet_netif.c;
ipcom_drv_eth_output():调用muxSend(),位于components/ip_net2-6.6/ipcom/port/vxworks/src/ipcom_drv_eth.c;
muxSend():位于components/ip_net2-6.6/ipcom/port/vxworks/src/ipcom_drv_eth.c,使用pEnd->pFuncTable->send()作为参数调用_muxTkSendEnd进行通用处理,参数检查失败则直接释放pMBlk发送缓冲区;
_muxTkSendEnd:位于components/ip_net2-6.6/ipcom/port/vxworks/src/ipcom_drv_eth.c,进行通用处理:
更新2233 MIB中的发包统计信息;
如果目的MAC地址非空,则调用pEnd->pFuncTable->formAddress即m5200FecEndLoad安装的endEtherAddressForm构造以太网头并放到pMBlk发送缓冲区;
如果网口已绑定协议栈,则调用pEnd->pFuncTable->packetDataGet即endEtherPacketDataGet获取pMBlk发送缓冲区的数据指针,然后调用muxEndRcvRtn接收数据到协议栈;
调用muxSend传递过来钩子调用pEnd->pFuncTable->send(),即m5200FecEndLoad函数安装的m5200FecSend();
如果发送阻塞即网口忙,则从pMBlk发送缓冲区移除以太网头,返回错误信息等待协议栈再次发送;
m5200FecSend():驱动接口,将数据写入发送缓冲区并发送,
检查参数和工作模式,失败则返回;
计算pMBlk发送缓冲区的分片数;
如果发送缓冲区描述符个数为0,则调用m5200FecTbdClean清理发送缓冲区;
如果送缓冲区描述符个数大于pMBlk发送缓冲区的分片数、缓冲区地址满足对齐要求且m5200FecForceCopy为false,则调用m5200FecPktTransmit;否则,调用m5200FecPktCopyTransmit;
m5200FecPktTransmit():驱动接口,使用pMBlk发送缓冲区进行零拷贝传输:
调用m5200FecTbdListSet获取发送缓冲区描述符列表;
使用将pMBlk发送缓冲区各分片对应的数据指针设置发送缓冲区描述符的缓冲区指针并更新发送缓冲区描述符标志;
调用TaskStart启动BestComm发送任务;
调用CACHE_PIPE_FLUSH刷新写缓冲;
m5200FecPktCopyTransmit():驱动接口,从驱动创建的netpool中申请新的MBLK用于保存pMBlk发送缓冲区中的所有数据,然后发送:
调用NET_BUF_ALLOC申请cluster即数据缓冲区,申请失败则使用临时发送缓冲区,仍然失败则返回发送阻塞;
对齐发送缓冲区数据地址到32字节;
调用m5200FecTbdListSet获取发送缓冲区描述符列表;
使用将pMBlk发送缓冲区各分片对应的数据拷贝到申请的新发送缓冲区中;
使用新发送缓冲区的指针设置发送缓冲区描述符的缓冲区指针并更新发送缓冲区描述符标志;
调用TaskStart启动BestComm发送任务;
调用CACHE_PIPE_FLUSH刷新写缓冲;
m5200FecWdmaInt():驱动接口,BestComm发送任务完成后会触发BestComm发送任务中断,并调用该函数:
调用SDMA_INT_DISABLE关闭BestComm发送任务中断;
调用TaskIntClear清除BestComm发送任务中断;
调用CACHE_PIPE_FLUSH刷新写缓冲;
调用netJobAdd将m5200FecTxHandle添加到tNet0任务维护的netJobQueueId队列,失败则调用SDMA_INT_ENABLE使能BestComm发送任务退出;
m5200FecTxHandle():清理发送缓冲区描述符和BestComm发送任务:
调用m5200FecTbdClean清理发送缓冲区;
调用intLock锁中断;
如果BestComm发送任务状态非空,则清除当前BestComm发送任务状态,调用intUnlock解锁中断,再次回到调用m5200FecTbdClean清理发送缓冲区,直到BestComm发送任务状态为空再调用intUnlock解锁中断并退出。
2.3 接收流程
接收流程的用户程序入口为socket库提供的recv/recvfrom/recvmsg()和ios子系统提供的read():
recv/recvfrom/recvmsg():调用协议栈利用sockLibAdd()注册的SOCK_FUNC指针中的recvRtn/recvfromRtn/recvmsgRtn(),即ipcom_windnet_recv/recvfrom/recvmsg()位于vxworks-6.6/target/src/wrn/coreip/sysdep/os/vxWorks/socket/sockLib.c;
read():调用socket库安装到ios子系统的socket驱设备的socketRead()钩子,进而调用协议栈利用sockLibAdd()注册的SOCK_FUNC指针中的socketresdRtn(),即ipcom_windnet_socketread(),位于vxworks-6.6/target/src/wrn/coreip/sysdep/os/vxWorks/socket/sockLib.c;
ipcom_windnet_recv/socketread():调用ipcom_recv(),进而调用ipcom_recvfrom,位于components/ip_net2-6.6/ipnet2/src/ipnet_sock.c;
ipcom_windnet_recvfrom/recvmsg():调用ipcom_recvfrom/recvmsg(),位于components/ip_net2-6.6/ipnet2/src/ipnet_sock.c;
ipcom_recvfrom():调用ipcom_recvmsg(),位于components/ip_net2-6.6/ipnet2/src/ipnet_sock.c;
ipcom_recvmsg():调用sock->ops->recv(),即ipnet_init()注册并由socket()接口使用ipcom_socket()安装的iptcp_recv和ipnet_sock_pkt_recv接口;
iptcp_recv/ipnet_sock_pkt_recv():如果socket的接收缓冲区中没有报文,则根据用户传入的标志决定直接返回或者挂起任务等待报文到来;否则,则取出报文,处理后返还给用户程序入口。
接收流程的驱动程序入口为驱动的m5200FecRdmaInt:
m5200FecRdmaInt():调用m5200FecRxHandle接收数据:
读取接收FIFO状态并打印调试信息;
调用intLock锁中断;
调用SDMA_INT_DISABLE关闭接收DMA任务中断;
调用TaskIntClear清除BestComm接收任务中断;
调用intUnlock解锁中断;
调用CACHE_PIPE_FLUSH刷新写缓冲;
调用netJobAdd将m5200FecRxHandle添加到tNet0任务维护的netJobQueueId队列并退出;
m5200FecRxHandle():处理BestComm接收任务中断,
调用m5200FecHandleRecvInt接收数据;
调用intLock锁中断;
如果仍有BestComm接收任务中断待处理,则调用TaskIntClear清除BestComm接收任务中断,调用intUnlock解锁中断,调用netJobAdd将m5200FecRxHandle添加到tNet0任务维护的netJobQueueId队列,调用intLock锁中断,并再次回到第一步调用m5200FecHandleRecvInt接收数据,直到没有BestComm接收任务中断需要处理,才调用SDMA_INT_ENABLE使能BestComm接收任务中断并退出;
m5200FecHandleRecvInt():遍历接收缓冲区描述符环形队列,调用m5200FecReceive接收数据并上送协议栈;
m5200FecReceive():接收数据并上送协议栈,
如果接收缓冲区描述符有错误,则调用m5200FecRbdClean清除接收缓冲区描述符并退出;
从驱动的netpool缓冲池申请MBLK和cluster,并关联到接收缓冲区描述符的缓冲区指针,同时从驱动的netpool缓冲池申请新的缓冲区用以更新接收缓冲描述符的缓冲区指针;
调用m5200FecRbdClean清除接收缓冲区描述符;
调用END_RCV_RTN_CALL上传数据到协议栈。
END_RCV_RTN_CALL:调用pEnd->receiveRtn即muxReceive,失败则调用netMblkClChainFree释放发送缓冲区pMBLK;
muxReceive():接收数据上传协议栈:
调用pEnd->pFuncTable->packetDataGet即endEtherPacketDataGet获取pMBlk接收缓冲区的数据指针;
获取以太网头中的报文类型;
遍历绑定在网口上的协议栈,若匹配则调用协议栈的接收函数pProto->rr.endRcv即muxEndRcvRtn将数据放入协议栈的缓冲区;
更新2233 MIB接收统计信息;
调用netMblkClChainFree释放发送缓冲区`pMBLK。
muxEndRcvRtn():调用pBinding->stackRcvRtn,即协议栈调用muxBind绑定的deng stackRcvRtn函数指针将数据上传到协议栈的缓冲区。
3. ifconfig up/down流程
ifconfig的入口位于vxworks-6.6/target/src/wrn/coreip/wrapper/utilslib/ifconfig.c,主要流程如下所示:
ifconfig():调用ipnet_cmd_ifconfig(),位于vxworks-6.6/target/src/wrn/coreip/wrapper/utilslib/ifconfig.c;
ipnet_cmd_ifconfig():调用ipcom_socket()创建socket,然后使用IP_TRUE和IP_FALSE调用ipnet_ifconfig_if_change_state(),位于components/ip_net2-6.6/ipnet2/src/ipnet_cmd_ifconfig.c;
ipnet_ifconfig_if_change_state():使用IP_SIOCSIFFLAGS,并设置或清除IP_IFF_UP来调用ipcom_socketioctl(),位于
components/ip_net2-6.6/ipnet2/src/ipnet_ioctl.c;
ipcom_socketioctl():调用ipnet_do_ioctl(),位于components/ip_net2-6.6/ipnet2/src/ipnet_ioctl.c;
ipnet_do_ioctl():调用ipnet_ioctl_if(),位于components/ip_net2-6.6/ipnet2/src/ipnet_ioctl.c;
ipnet_ioctl_if():使用IP_SIOCXOPEN或IP_SIOCXCLOSE调用ipnet_if_link_ioctl(),位于components/ip_net2-6.6/ipnet2/src/ipnet_netif.c;
ipnet_if_link_ioctl():调用netif->link_ioctl(),即通过ipnet_eth_if_init()安装的ipnet_eth_ioctl(),位于components/ip_net2-6.6/ipnet2/src/ipnet_netif.c;
ipnet_eth_ioctl():调用ipnet_if_drv_ioctl(),位于components/ip_net2-6.6/ipnet2/src/ipnet_eth.c;