1. 开发环境
操作系统:SylixOS
编程环境:RealEvo-IDE3.1
硬件平台:IMX6Q实验箱
2. 技术实现
网卡驱动的收发功能,是通过管理收发描述符的方式实现的。因此,在MAC初始化的时候需要对描述符也进行相应的初始化操作。初始化内容会因CPU的不同而有所区别。当描述符初始化完毕之后,就可以用他们来进行网络报文的收发。
2.1 网络发送函数的实现
网络驱动的发送函数通过enetCoreTx函数实现,具体实现如程序清单 2-1。
程序清单 2-1 发送函数
/********************************************************************************************************* ** 函数名称: enetCoreTx ** 功能描述: 网络发送函数 ** 输 入 : pNetDev : 网络结构 ** pbuf : lwip 核心 buff ** 输 出 : 错误号 ** 全局变量: ** 调用模块: *********************************************************************************************************/ static INT enetCoreTx (struct netdev *pNetDev, struct pbuf *pbuf) { ENET *pEnet; addr_t atBase; UINT16 usStatus; UINT16 usLen; BUFD *pbufd; INT iLinkUp; INTREG iregFlag; pEnet = pNetDev->priv; atBase = pEnet->ENET_atIobase; /* * 如果网络是断开状态,返回错误,flags 在用户程序中为只读,其设置在 * link_down link_up 函数中设置 */ netdev_get_linkup(pNetDev, &iLinkUp); if(!iLinkUp) { return (PX_ERROR); } KN_SMP_WMB(); LW_SPIN_LOCK_QUICK(&pEnet->ENET_slLock, &iregFlag); pbufd = pEnet->ENET_pbufdCurTxbd; usStatus = pbufd->BUFD_usStatus; if (usStatus & ENET_BD_TX_READY) { LW_SPIN_UNLOCK_IRQ(&pEnet->ENET_slLock, iregFlag); printk("Send queue full!!\n"); return (PX_ERROR); } KN_SMP_WMB(); usStatus &= ~ENET_BD_TX_STATS; usLen = pbuf->tot_len; usStatus |= ENET_BD_TX_TC | ENET_BD_TX_LAST | ENET_BD_TX_READY | ENET_BD_TX_TO2; pbuf_copy_partial(pbuf, (PVOID)pbufd->BUFD_uiBufAddr, usLen, 0); KN_SMP_WMB(); pbufd->BUFD_usDataLen = usLen; pbufd->BUFD_usStatus = usStatus; writel(ENET_TDAR_TX_ACTIVE, atBase + HW_ENET_MAC_TDAR); /* 使能 enet 控制器发送,原有位置 */ /* * 如果这是最后一个发送描述符,返回最开始 */ if (usStatus & ENET_BD_TX_WRAP) { pbufd = pEnet->ENET_pbufdTxbdBase; } else { pbufd++; } if (pbufd == pEnet->ENET_pbufdTxDirty) { pEnet->ENET_iFull = 1; } pEnet->ENET_pbufdCurTxbd = pbufd; netdev_statinfo_total_add(pNetDev, LINK_OUTPUT, usLen); if (((UINT8 *)pbuf->payload)[0] & 1) { netdev_statinfo_mcasts_inc(pNetDev, LINK_OUTPUT); /* 统计发送广播数据包数 */ } else { netdev_statinfo_ucasts_inc(pNetDev, LINK_OUTPUT); /* 统计发送单播数据包数 */ } LW_SPIN_UNLOCK_QUICK(&pEnet->ENET_slLock, iregFlag); return (ERROR_NONE); }
在enetCoreTx函数中,首先会检测网络当前的连接状态,只有在连接成功的状态下,才能进行发送操作。
网络处于连接成功状态后,会去判断一下当前的描述符是否可用。一旦发送描述符可以操作,就会去填充描述符。填充的内容主要有报文长度,报文地址,描述符的状态等。这个也需要根据不同的描述符定义来做相应的处理。
描述符填充完成之后,就可以启动MAC进行发送操作。
完成上述操作之后,使用我们系统提供的netdev_statinfo_total_add、netdev_statinfo_mcasts_inc、netdev_statinfo_ucasts_inc这三个函数来进行发送报文的统计。
2.2 网络接收函数的实现
网络驱动处理接收任务的函数enetCoreRecv,需要通过中断来进行调用。中断服务函数如程序清单 2-2 。
程序清单 2-2 中断服务函数
/********************************************************************************************************* ** 函数名称: enetIsr ** 功能描述: 以太网中断响应函数 ** 输 入 : pvArg : 中断参数 ** uiVector: 中断向量号 ** 输 出 : 中断返回值 ** 全局变量: ** 调用模块: *********************************************************************************************************/ static irqreturn_t enetIsr (PVOID pvArg, UINT32 uiVector) { struct netdev *pNetDev = (struct netdev *)pvArg; ENET *pEnet; INT ie = 0; addr_t atBase; pEnet = pNetDev->priv; atBase = pEnet->ENET_atIobase; ie = readl(atBase + HW_ENET_MAC_EIR); writel(ie, atBase + HW_ENET_MAC_EIR); KN_SMP_WMB(); if (ie & ENET_EIR_TXF) { /* 已经完成了发送 */ enetCoreSendComplete(pNetDev); return (LW_IRQ_HANDLED); } if (ie & ENET_EIR_RXF) { /* 已经完成了接收 */ netdev_notify(pNetDev, LINK_INPUT, 1); writel(ENET_EIR_TXF, atBase + HW_ENET_MAC_EIMR); /* 关闭接收中断 */ } if (ie & ENET_EIR_MII) { /* * MII 读写事件,由于本驱动没有使用 MII 的中断,因此此部分代码无实际用途,调试时使用 */ } return (LW_IRQ_HANDLED);
中断服务会去判断一下当前是什么原因触发的中断,如果是接收完成,则调用系统的通知函数netdev_notify,通知协议栈当前已经收到网络报文,需要进行处理。如果netdev_notify函数的第三个参数为1,那么会将接收处理函数enetCoreRecv加入网络处理队列,去处理接收到的报文。enetCoreRecv函数如程序清单 23。
程序清单 2-3 接受处理函数
/********************************************************************************************************* ** 函数名称: enetCoreRecv ** 功能描述: 网络接收函数 ** 输 入 : pNetDev : 网络结构 ** 输 出 : 接收的长度 ** 全局变量: ** 调用模块: *********************************************************************************************************/ static VOID enetCoreRecv (struct netdev *pNetDev, INT (*input)(struct netdev *, struct pbuf *)) { ENET *pEnet; struct pbuf *pBuf; UINT8 *ucFrame; BUFD *pBufd; addr_t atBase; UINT16 usStatus; UINT16 usLen = 0; pEnet = pNetDev->priv; atBase = pEnet->ENET_atIobase; KN_SMP_WMB(); pBufd = pEnet->ENET_pbufdCurRxbd; while (!((usStatus = pBufd->BUFD_usStatus) & ENET_BD_RX_EMPTY)) { if (usStatus & ENET_BD_RX_LG) { #if LINK_STATS netdev_linkinfo_lenerr_inc(pNetDev); #endif } if (usStatus & ENET_BD_RX_CR) { #if LINK_STATS netdev_linkinfo_chkerr_inc(pNetDev); #endif } if (usStatus & ENET_BD_RX_OV) { #if LINK_STATS netdev_linkinfo_memerr_inc(pNetDev); #endif } if (usStatus & ENET_BD_RX_TR) { #if LINK_STATS netdev_linkinfo_err_inc(pNetDev); netdev_linkinfo_memerr_inc(pNetDev); #endif goto rx_done; } usLen = pBufd->BUFD_usDataLen; ucFrame = (UINT8 *)pBufd->BUFD_uiBufAddr; /* 获取接收的帧 */ usLen -= 4; /* 除去 FCS */ pBuf = netdev_pbuf_alloc(usLen); if (!pBuf) { #if LINK_STATS netdev_linkinfo_memerr_inc(pNetDev); netdev_linkinfo_drop_inc(pNetDev); #endif netdev_statinfo_discards_inc(pNetDev, LINK_INPUT); } else { pbuf_take(pBuf, ucFrame, (UINT16)usLen); KN_SMP_WMB(); if (input(pNetDev, pBuf)) { /* 提交数据到协议栈 */ netdev_pbuf_free(pBuf); netdev_statinfo_discards_inc(pNetDev, LINK_INPUT); } else { #if LINK_STATS netdev_linkinfo_recv_inc(pNetDev); #endif netdev_statinfo_total_add(pNetDev, LINK_INPUT, usLen); /* 统计发送数据长度 */ if (((UINT8 *)pBuf->payload)[0] & 1) { netdev_statinfo_mcasts_inc(pNetDev, LINK_INPUT); /* 统计发送广播数据包数 */ } else { netdev_statinfo_ucasts_inc(pNetDev, LINK_INPUT); /* 统计发送单播数据包数 */ } } } rx_done: usStatus &= ~ENET_BD_RX_STATS; usStatus |= ENET_BD_RX_EMPTY; pBufd->BUFD_usStatus = usStatus; if (usStatus & ENET_BD_RX_WRAP) { pBufd = pEnet->ENET_pbufdRxbdBase; } else { pBufd++; } KN_SMP_WMB(); writel(ENET_RDAR_RX_ACTIVE, atBase + HW_ENET_MAC_RDAR); /* 使能 enet 控制器接收 */ } pEnet->ENET_pbufdCurRxbd = pBufd; writel(ENET_DEFAULT_INTE, atBase + HW_ENET_MAC_EIMR); }
enetCoreRecv函数里是一个循环处理,循环执行的条件是当前的接收描述符正确收到报文。
while循环里,会先通过描述符拿到接收到的报文的长度以及报文存放的地址。知道报文存放的位置之后,可以通过pbuf_take函数,将报文拷贝到pbuf里。一旦拷贝成功,就可以通过enetCoreRecv的第二个参数input,将pbuf提交到协议栈。
成功提交之后,就可以像发送函数一样,调用netdev_linkinfo_recv_inc、netdev_statinfo_total_add、netdev_statinfo_mcasts_inc、netdev_statinfo_ucasts_inc这几个函数,进行接收报文的统计操作。
最后还需要对描述符进行一些更新或归还操作,让其能再次被用来接受报文。
3.参考资料
无