3 onps栈移植说明——添加网卡

4. 添加网卡移植的最后一步就是编写网卡驱动然后将网卡添加到协议栈 。网卡驱动其本质上完成的是数据链路层的工作,在整个通讯链路上处于通讯枢纽位置,通讯报文的发送和接收均由其实际完成 。针对网卡部分的移植工作共三步:
1)编写网卡驱动;
2)注册网卡到协议栈;
3)对接网卡数据收发接口;
协议栈目前支持两种网卡类型:ethernet和ppp 。两种网卡的移植工作虽然步骤一样,但具体移植细节还是有很大区别的,需要分开单独进行 。
4.1 ethernet网卡从移植的角度看,ethernet网卡驱动要提供三个接口函数并完成与协议栈的对接:
1)网卡初始化函数,完成网卡初始及启动工作,并将其添加到协议栈;
2)网卡发送函数,发送上层协议传递的通讯报文到对端;
3)网卡接收函数,接收到达的通讯报文并传递给上层协议;
对于网卡初始化函数,其要做的工作用一句话总结就是:参照网卡数据手册对其进行配置,然后将其注册到协议栈:
#define DHCP_REQ_ADDR_EN 1 //* dhcp请求ip地址使能宏static PST_NETIF l_pstNetifEth = NULL; //* 协议栈返回的netif结构int ethernet_init(void){/* 进行初始配置 , 比如引脚配置、使能时钟、相关工作参数配置等工作 *///* 在这里添加能够完成上述工作的相关代码,请参照目标网卡的技术手册编写…………/* 到这里网卡配置工作完成,但还未启动 *///* 添加网卡到协议栈 , 一定要注意启动以太网卡之前一定要先将其添加到协议栈EN_ONPSERR enErr;ST_IPV4 stIPv4;#if !DHCP_REQ_ADDR_EN//* 分配一个静态地址,请根据自己的具体网络情形设置地址stIPv4.unAddr = inet_addr_small("192.168.0.4");stIPv4.unSubnetMask = inet_addr_small("255.255.255.0");stIPv4.unGateway = inet_addr_small("192.168.0.1");stIPv4.unPrimaryDNS = inet_addr_small("1.2.4.8");stIPv4.unSecondaryDNS = inet_addr_small("8.8.8.8");stIPv4.unBroadcast = inet_addr_small("192.168.0.255");#else//* 地址清零,为dhcp客户端申请动态地址做好准备memset(&stIPv4, 0, sizeof(stIPv4));#endif//* 注册网卡,也就是将网卡添加到协议栈l_pstNetifEth = ethernet_add(……);if(!l_pstNetifEth){#if SUPPORT_PRINTFprintf("ethernet_add() failed, %s\r\n", onps_error(enErr));#endifreturn -1;}//* 启动网卡,开始工作,在这里添加与目标网卡启动相关的代码……#if DHCP_REQ_ADDR_EN//* 启动一个dhcp客户端,从dhcp服务器申请一个动态地址if(dhcp_req_addr(l_pstNetifEth, &enErr)){#if SUPPORT_PRINTFprintf("dhcp request ip address successfully.\r\n");#endif}else{#if SUPPORT_PRINTFprintf("dhcp request ip address failed, %s\r\n", onps_error(enErr));#endif}#endifreturn 0;}上面给出的样例代码中 , 省略的部分是与目标系统相关的网卡初始配置代码,其余则是与协议栈有关的网卡注册代码 。这部分代码主要是完成了两块工作:一,注册网卡到协议栈;二,指定或申请一个静态/动态地址 。注册网卡的工作是由协议栈提供的ethernet_add()函数完成的,其详细说明如下:
//* 注册ethernet网卡到协议栈,只有如此协议栈才能正常使用该网卡进行数据通讯 。//*pszIfName:网卡名称//*ubaMacAddr:网卡mac地址//*pstIPv4:指向ST_IPV4结构体的指针(include/netif/netif.h),这个结构体保存用户指定的ip地址、网关、dns、子网掩码等配置信息//*//*pfunEmacSend:函数指针,指向发送函数,函数原型为INT(* PFUN_EMAC_SEND)(SHORT sBufListHead, UCHAR *pubErr),这个指针指向的其实//*就是网卡发送函数//*//* pfunStartTHEmacRecv:函数指针,协议栈使用该函数启动网卡接收线程,该线程为协议栈内部工作线程,用户移植时只需提供启动该线程的接口函数即可//*//*ppstNetif:二维指针,协议栈成功注册网卡后ethernet_add()函数会返回一个PST_NETIF指针给调用者,这个参数指向这个指针,其最终会被//*协议栈通过pvParam参数传递给pfunStartTHEmacRecv指向的函数//*//*penErr:如果注册失败,ethernet_add()函数会返回一个错误码,这个参数用于接收这个错误码//*//* 注册成功,返回一个PST_NETIF类型的指针,后续的报文收发均用到这个指针;注册失败则返回NULL 。具体错误信息参见penErr参数携带的错误码 。PST_NETIF ethernet_add(const CHAR *pszIfName, const UCHAR ubaMacAddr[ETH_MAC_ADDR_LEN], PST_IPV4 pstIPv4, PFUN_EMAC_SEND pfunEmacSend,void (*pfunStartTHEmacRecv)(void *pvParam), PST_NETIF *ppstNetif, EN_ONPSERR *penErr);ethernet_add()函数提供的参数看起来较为复杂,但其实就完成了一件事情:告诉协议栈这个新增加的网卡的相关身份信息及功能接口,包括名称、地址、数据读写接口等 。这个函数有两个地方需要特别说明:一个是样例代码中该函数的返回值保存在了一个静态存储时期的变量l_pstNetifEth中;另一个是入口参数pfunStartTHEmacRecv 。前一个用于接收注册成功后返回的PST_NETIF指针;后一个则是需要提供一个线程启动函数,启动协议栈内部的以太网接收线程thread_ethernet_ii_recv(),该线程在协议栈源码ethernet.c文件中实现 。PST_NETIF指针非常重要,它是网卡能够正常工作的关键 。报文收发均用到这个指针 。它的生命周期应该与协议栈的生命周期相同,因此这个指针变量在上面的样例代码中被定义成一个静态存储时期的变量,并确保网卡的接收、发送函数均能访问 。pfunStartTHEmacRecv参数指向的函数要实现的功能与前面我们编写的os适配层函数os_thread_onpstack_start()相同,其就是调用os提供的线程启动函数启动thread_ethernet_ii_recv()线程 。比如rt-thread下:

推荐阅读