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函数如程序清单 2-3。
程序清单 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.参考资料
无