VxWorks 6.6网络协议栈分析及网络接口基本知识

本文包含网络接口的硬件基本原理和VxWorks 6.6网络协议栈/驱动的分析,适用于VxWorks6.6以及之前的版本,并可用作其他版本协议栈的参考。

0. 概述

0.1 网络接口

以太网目前有两种模型,即ISO(国际标准化组织)定义的的OSI七层模型和IETF(互联网工程任务组)的TCP/IP五层模型,具体可参考TCP/IP协议详解。目前,TCP/IP为互联网的事实标准,具体分层如下所示:

上表中:

  • 以太网报文格式与上表的分层对应:
  • MAC又称为以太网控制器,既具有独立的链路层地址(MAC地址,又称为以太网地址),也就是通常意义上或者狭义上的网口,用于控制链路层数据的传输;
  • PHY用于控制物理层传输,用于物理链路与MAC之间数据的编解码、物理链路控制、载波侦听、线序交换等功能,通常只包含PCS(物理编码子层)、PMD(物理介质相关子层)、PMA(物理介质附加子层)等,但是1000Mbps以上的高速链路需要更多的子层(DTE XGXS、PHY XGXS);
  • MAC通过MII(狭义上的介质独立接口)/RGMII(简化的千兆介质无关接口)/SGMII(串行千兆介质无关接口)/XGMII(超高速介质无关接口)总线等连接到PHY上;
  • 每个MAC通常只需要一个PHY,但是1000Mbps以上的高速网口的PHY设备需要可能还需要先通过内部PHY将TBI接口转换成SGMII接口或者将XGMII接口转换成XAUI接口,再连接到外部PHY,从而降低外部PHY的布线难度;
  • TBI、XAUI和MII、RGMII、SGMII、XGMII、GMII、QGMII等接口都属于广义上的MII接口,对应于MII总线;
  • PHY必须挂在MII总线上,通过MII控制器访问;
  • MII接口有MDIO22和MIDO45两种标准,分别定义在802.3ap clasue22和Clasue 45中, 前者支持5位PHY地址和5位寄存器地址,后者增加了5位设备地址,允许控制PHY的不同子层;因此,每个MII控制器最多通过MII总线支持32个PHY设备
  • 广义上的网口指的是MAC、MII控制器和PHY的综合体。

0.2 约定

  • socket即套接字,是BSD协议栈提出的网络接口,包含一个接收缓冲区,用于收发数据和控制数据传输;
  • MBLK/Cluster/data是BSD协议栈提出的缓冲区模型,每个数据区data对应一个Cluster,每个Cluster关联到1个MBLK上;MBLK作为以太网报文的代表,可以将多个以太网报文分片串成一个链表,用以提升数据收发效率;
  • 分析以VxWorks6.6上的MPC5200 FEC网口为例,适用于VxWorks6.6以及之前的版本,并可用作其他版本协议栈的参考;
  • 流程图中的非关键函数参数和非关键流程被省略以突出重点和降低工作量;

1. END驱动架构

END驱动整体架构如下所示:

其中:

1. 用户可以通过3种接口访问协议栈接口:

  • 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等;

2. 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注册的对应钩子函数;

3. 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()接口来完成协议栈绑定、、驱动加载、网口控制和数据收发功能;

4. 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->receiveRtnmuxReceivem5200FecEnd类型为END_STYLE_END);

5. end层用于提供网卡驱动的封装结构和公用代码。

2. 网卡驱动全工作流程

2.1 初始化流程

网卡驱动初始化入口为usrNetworkInit():

  1. usrNetworkInit:该位于<工程目录>/prjConfig.c中,调用usrNetEndLibInit(),用于初始化网卡驱动;
  2. usrNetEndLibInit:该接口位于vxworks-6.6/target/src/config/usrNetwork.cvxworks-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()用于初始化网口驱动的统计信息。

3. muxDevLoad():位于vxworks-6.6/target/src/wrn/coreip/common/mux/muxLib.c中,用于调用endDevTbl[]数组中Legacy网口驱动的初始化函数endLoad,关键流程如下所示:

其中:

  • 第一次调用endLoad时参数为空字符串,用于获取网口名称,例如’fec’;
  • 第二次调用endLoad时参数为网口单元号与初始化字符串的组合,用于正式加载网口;

4. muxDevStart():位于vxworks-6.6/target/src/wrn/coreip/common/mux/muxLib.c中,用于调用驱动注册的pEnd->pFuncTable->start()接口启动网口,并将网口设置为IFF_UP | IFF_RUNNING状态

5. m5200FecEndLoad():驱动接口,主要功能是注册到endLib:

  • 分配驱动信息结构和PHY信息结构;
  • 调用m5200FecInitParse解析初始化字符串并保存在驱动信息结构中;
  • 调用m5200FecInitMem申请临时发送缓冲区,并创建netpool网络缓冲池;
  • 调用m5200FecSdmaTaskInit创建Bestcomm SDMA任务;
  • 调用END_OBJ_INITendObjInit初始化END结构,注册网口驱动功能列表;
  • 调用END_FLAGS_SET设置多播和广播标志;

6. m5200FecStart():驱动接口,主要功能是启动网口:

  • 调用m5200FecReset复位以太网控制器;
  • 调用m5200FecTbdInit/m5200FecRbdInit初始化收发缓冲区描述符环;
  • 调用SYS_FEC_INT_CONNECTintConnect挂接BestComm发送和接收任务中断以及通用中断处理函数;
  • 调用SYS_FEC_INT_ENABLEintEnable使能中断;
  • 调用m5200FecPrePhyConfig初始化MAC地址、设置中断事件掩码、清除中断事件、配置内部MII控制器接口等;
  • 调用m5200FecPhyPreInit根据驱动信息结构中的标志设置PHY信息结构中的工作模式标志;
  • 调用_func_m5200FecPhyInitm5200FecPhyInit初始化PHY,并根据PHY信息结构中的工作模式标志选择自协商模式或强制模式;
  • 调用TaskIntClear清除Bsetcomm接收任务中断;
  • 调用TaskStart启动Bsetcomm接收任务;
  • 调用FEC_END_ETH_ENABLE使能以太网控制器;
  • 调用END_FLAGS_SET将网口标志为IFF_UP | IFF_RUNNING,即工作状态;
  • 调用netJobAddmuxTxRestart添加到tNet0任务维护的netJobQueueId队列,进而调用绑定在网口上的协议栈的stackTxRestartRtn接口以复位协议栈。

2.2 发送流程

发送流程的入口为socket库提供的send/sendto/sendmsg()和ios子系统提供的write()

  1. 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
  2. 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
  3. ipcom_windnet_send/sendto/sendmsg/socketwrite():调用ipcom_send/sendto/sendmsg/send(),位于components/ip_net2-6.6/ipnet2/src/ipnet_sock.c;
  4. ipcom_send/sendto/sendmsg/send():调用ipcom_sendmsg(),位于components/ip_net2-6.6/ipnet2/src/ipnet_sock.c;
  5. ipcom_sendmsg():调用sock->ops->send(),即ipnet_init()注册并由socket()接口使用ipcom_socket()安装的iptcp_send和ipnet_sock_udp_send接口;
  6. 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

7. ipnet_eth_ip4_output():调用ipnet_if_output(),位于components/ip_net2-6.6/ipnet2/src/ipnet_eth.c

8. ipnet_if_output():调用ipnet_if_drv_output(),位于components/ip_net2-6.6/ipnet2/src/ipnet_netif.c

9. 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

10. ipcom_drv_eth_output():调用muxSend(),位于components/ip_net2-6.6/ipcom/port/vxworks/src/ipcom_drv_eth.c

11. muxSend():位于components/ip_net2-6.6/ipcom/port/vxworks/src/ipcom_drv_eth.c,使用pEnd->pFuncTable->send()作为参数调用_muxTkSendEnd进行通用处理,参数检查失败则直接释放pMBlk发送缓冲区;

12. _muxTkSendEnd:位于components/ip_net2-6.6/ipcom/port/vxworks/src/ipcom_drv_eth.c,进行通用处理:

  • 更新2233 MIB中的发包统计信息;
  • 如果目的MAC地址非空,则调用pEnd->pFuncTable->formAddressm5200FecEndLoad安装的endEtherAddressForm构造以太网头并放到pMBlk发送缓冲区;
  • 如果网口已绑定协议栈,则调用pEnd->pFuncTable->packetDataGetendEtherPacketDataGet获取pMBlk发送缓冲区的数据指针,然后调用muxEndRcvRtn接收数据到协议栈;
  • 调用muxSend传递过来钩子调用pEnd->pFuncTable->send(),即m5200FecEndLoad函数安装的m5200FecSend()
  • 如果发送阻塞即网口忙,则从pMBlk发送缓冲区移除以太网头,返回错误信息等待协议栈再次发送;

13. m5200FecSend():驱动接口,将数据写入发送缓冲区并发送,

  • 检查参数和工作模式,失败则返回;
  • 计算pMBlk发送缓冲区的分片数;
  • 如果发送缓冲区描述符个数为0,则调用m5200FecTbdClean清理发送缓冲区;
  • 如果送缓冲区描述符个数大于pMBlk发送缓冲区的分片数、缓冲区地址满足对齐要求且m5200FecForceCopy为false,则调用m5200FecPktTransmit;否则,调用m5200FecPktCopyTransmit

14. m5200FecPktTransmit():驱动接口,使用pMBlk发送缓冲区进行零拷贝传输:

  • 调用m5200FecTbdListSet获取发送缓冲区描述符列表;
  • 使用将pMBlk发送缓冲区各分片对应的数据指针设置发送缓冲区描述符的缓冲区指针并更新发送缓冲区描述符标志;
  • 调用TaskStart启动BestComm发送任务;
  • 调用CACHE_PIPE_FLUSH刷新写缓冲;

15. m5200FecPktCopyTransmit():驱动接口,从驱动创建的netpool中申请新的MBLK用于保存pMBlk发送缓冲区中的所有数据,然后发送:

  • 调用NET_BUF_ALLOC申请cluster即数据缓冲区,申请失败则使用临时发送缓冲区,仍然失败则返回发送阻塞;
  • 对齐发送缓冲区数据地址到32字节;
  • 调用m5200FecTbdListSet获取发送缓冲区描述符列表;
  • 使用将pMBlk发送缓冲区各分片对应的数据拷贝到申请的新发送缓冲区中;
  • 使用新发送缓冲区的指针设置发送缓冲区描述符的缓冲区指针并更新发送缓冲区描述符标志;
  • 调用TaskStart启动BestComm发送任务;
  • 调用CACHE_PIPE_FLUSH刷新写缓冲;

16. m5200FecWdmaInt():驱动接口,BestComm发送任务完成后会触发BestComm发送任务中断,并调用该函数:

  • 调用SDMA_INT_DISABLE关闭BestComm发送任务中断;
  • 调用TaskIntClear清除BestComm发送任务中断;
  • 调用CACHE_PIPE_FLUSH刷新写缓冲;
  • 调用netJobAddm5200FecTxHandle添加到tNet0任务维护的netJobQueueId队列,失败则调用SDMA_INT_ENABLE使能BestComm发送任务退出;

17. m5200FecTxHandle():清理发送缓冲区描述符和BestComm发送任务:

  • 调用m5200FecTbdClean清理发送缓冲区;
  • 调用intLock锁中断;
  • 如果BestComm发送任务状态非空,则清除当前BestComm发送任务状态,调用intUnlock解锁中断,再次回到调用m5200FecTbdClean清理发送缓冲区,直到BestComm发送任务状态为空再调用intUnlock解锁中断并退出。

2.3 接收流程

接收流程的用户程序入口为socket库提供的recv/recvfrom/recvmsg()和ios子系统提供的read()

  1. 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
  2. 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
  3. ipcom_windnet_recv/socketread():调用ipcom_recv(),进而调用ipcom_recvfrom,位于components/ip_net2-6.6/ipnet2/src/ipnet_sock.c;
  4. ipcom_windnet_recvfrom/recvmsg():调用ipcom_recvfrom/recvmsg(),位于components/ip_net2-6.6/ipnet2/src/ipnet_sock.c;
  5. ipcom_recvfrom():调用ipcom_recvmsg(),位于components/ip_net2-6.6/ipnet2/src/ipnet_sock.c;
  6. ipcom_recvmsg():调用sock->ops->recv(),即ipnet_init()注册并由socket()接口使用ipcom_socket()安装的iptcp_recv和ipnet_sock_pkt_recv接口;
  7. iptcp_recv/ipnet_sock_pkt_recv():如果socket的接收缓冲区中没有报文,则根据用户传入的标志决定直接返回或者挂起任务等待报文到来;否则,则取出报文,处理后返还给用户程序入口。

接收流程的驱动程序入口为驱动的m5200FecRdmaInt

  1. m5200FecRdmaInt():调用m5200FecRxHandle接收数据:
  • 读取接收FIFO状态并打印调试信息;
  • 调用intLock锁中断;
  • 调用SDMA_INT_DISABLE关闭接收DMA任务中断;
  • 调用TaskIntClear清除BestComm接收任务中断;
  • 调用intUnlock解锁中断;
  • 调用CACHE_PIPE_FLUSH刷新写缓冲;
  • 调用netJobAddm5200FecRxHandle添加到tNet0任务维护的netJobQueueId队列并退出;

2. m5200FecRxHandle():处理BestComm接收任务中断,

  • 调用m5200FecHandleRecvInt接收数据;
  • 调用intLock锁中断;
  • 如果仍有BestComm接收任务中断待处理,则调用TaskIntClear清除BestComm接收任务中断,调用intUnlock解锁中断,调用netJobAddm5200FecRxHandle添加到tNet0任务维护的netJobQueueId队列,调用intLock锁中断,并再次回到第一步调用m5200FecHandleRecvInt接收数据,直到没有BestComm接收任务中断需要处理,才调用SDMA_INT_ENABLE使能BestComm接收任务中断并退出;

3. m5200FecHandleRecvInt():遍历接收缓冲区描述符环形队列,调用m5200FecReceive接收数据并上送协议栈;

4. m5200FecReceive():接收数据并上送协议栈,

  • 如果接收缓冲区描述符有错误,则调用m5200FecRbdClean清除接收缓冲区描述符并退出;
  • 从驱动的netpool缓冲池申请MBLK和cluster,并关联到接收缓冲区描述符的缓冲区指针,同时从驱动的netpool缓冲池申请新的缓冲区用以更新接收缓冲描述符的缓冲区指针;
  • 调用m5200FecRbdClean清除接收缓冲区描述符;
  • 调用END_RCV_RTN_CALL上传数据到协议栈。

5. END_RCV_RTN_CALL:调用pEnd->receiveRtnmuxReceive,失败则调用netMblkClChainFree释放发送缓冲区pMBLK;

6. muxReceive():接收数据上传协议栈:

  • 调用pEnd->pFuncTable->packetDataGetendEtherPacketDataGet获取pMBlk接收缓冲区的数据指针;
  • 获取以太网头中的报文类型;
  • 遍历绑定在网口上的协议栈,若匹配则调用协议栈的接收函数pProto->rr.endRcvmuxEndRcvRtn将数据放入协议栈的缓冲区;
  • 更新2233 MIB接收统计信息;
  • 调用netMblkClChainFree释放发送缓冲区`pMBLK。

7. muxEndRcvRtn():调用pBinding->stackRcvRtn,即协议栈调用muxBind绑定的deng stackRcvRtn函数指针将数据上传到协议栈的缓冲区。

3. ifconfig up/down流程

ifconfig的入口位于vxworks-6.6/target/src/wrn/coreip/wrapper/utilslib/ifconfig.c,主要流程如下所示:

  1. ifconfig():调用ipnet_cmd_ifconfig(),位于vxworks-6.6/target/src/wrn/coreip/wrapper/utilslib/ifconfig.c
  2. 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
  3. ipnet_ifconfig_if_change_state():使用IP_SIOCSIFFLAGS,并设置或清除IP_IFF_UP来调用ipcom_socketioctl(),位于
    components/ip_net2-6.6/ipnet2/src/ipnet_ioctl.c
  4. ipcom_socketioctl():调用ipnet_do_ioctl(),位于components/ip_net2-6.6/ipnet2/src/ipnet_ioctl.c
  5. ipnet_do_ioctl():调用ipnet_ioctl_if(),位于components/ip_net2-6.6/ipnet2/src/ipnet_ioctl.c
  6. ipnet_ioctl_if():使用IP_SIOCXOPEN或IP_SIOCXCLOSE调用ipnet_if_link_ioctl(),位于components/ip_net2-6.6/ipnet2/src/ipnet_netif.c
  7. ipnet_if_link_ioctl():调用netif->link_ioctl(),即通过ipnet_eth_if_init()安装的ipnet_eth_ioctl(),位于components/ip_net2-6.6/ipnet2/src/ipnet_netif.c
  8. ipnet_eth_ioctl():调用ipnet_if_drv_ioctl(),位于components/ip_net2-6.6/ipnet2/src/ipnet_eth.c
  9. ipnet_if_drv_ioctl():调用ipnet_if_clean_snd_queue()清理并复位当前接口上的队列,然后调用netif->ipcom.drv_ioctl(),即通过ipcom_drv_eth_init()安装的ipcom_drv_eth_ioctl(),位于components/ip_net2-6.6/ipnet2/src/ipnet_netif.c
  10. ipcom_drv_eth_ioctl():为IP_SIOCXOPEN调用ipnet_drv_eth_sync_with_end_flags(),为IP_SIOCXCLOSE调用netif->link_ioevent即ipnet_eth_ioevent()通知协议栈网口处于IP_EIOXSTOP,位于components/ip_net2-6.6/ipnet2/src/ipnet_eth.c
  11. ipnet_drv_eth_sync_with_end_flags():调用muxIoctl获取网口标志和状态,若网口处于活动状态度,则调用netif->link_ioevent即ipnet_eth_ioevent()通知协议栈网口处于IP_EIOXRUNNING状态。

综上所述,ifconfig up/down不会真正操作网口驱动,只是清理并复位当前网口上的队列,然后通知协议栈网口处于IP_EIOXRUNNING或IP_EIOXSTOP状态。