SylixOS网卡驱动实现篇

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

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

##3.参考资料 无

转载于:https://my.oschina.net/SeanHa/blog/856164

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值