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_addnetdev_statinfo_mcasts_incnetdev_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_incnetdev_statinfo_total_addnetdev_statinfo_mcasts_incnetdev_statinfo_ucasts_inc这几个函数,进行接收报文的统计操作。

最后还需要对描述符进行一些更新或归还操作,让其能再次被用来接受报文。

3.参考资料