CPSW网卡移植字符设备网卡驱动笔记

字符设备网卡移植



1. 适用范围

适用于客户要求SylixOS不使用网络协议栈的情形下,网卡驱动通过字符设备驱动实现。

2. 移植概述

本次移植是以普通版本的SylixOS CPSW网卡,移植步骤如下:

1) 移植前先搭建测试环境,查看网络协议栈网卡驱动是否正常。

2) 了解带网络协议栈网卡驱动代码的驱动框架和寄存器作用。

3) 修改驱动代码,将所有涉及到lwip协议栈的头文件和结构体都注释掉,lwip协议栈的函数尽量重新实现完成相应功能。

4) 实现字符设备操作函数:openclosereadwriteioctl等函数。

5) 修改收发中断处理函数,描述符的维护。

6) 编写测试程序,测试字符网卡的基本功能。

7) 记录BUG问题,进行分析和总结。

3. 准备工作

3.1 环境准备

搭建普通版本SylixOS测试环境。目的:测试准备移植的带网络协议栈网卡驱动功能是否正常。

3.2 资源准备

根据bsp工程的README文件来进行搭建环境,烧录SylixOS镜像到板子上。

查看网卡是否配置成功图例如 3-1所示:

 

 

3-1 查看网络设备

 

查看带网络协议栈网卡驱动是否能和电脑进行互通,设置好板子和电脑同一网段,并且板子和电脑可以互通则网络协议栈网卡驱动正常,如 3-2所示:

 

3-2 查看网卡功能是否正常

4. 技术实现

4.1 了解网络协议栈网卡驱动框架

目的:了解网卡驱动框架和工作原理。

CPU:为MAC控制器提供描述符,以及负责在网络协议栈和网络驱动之间传送数据。

MAC:通过描述符来控制DMA发送和接收数据。

DMA:用来在缓存区和MAC之间搬送数据,一般是MAC控制器内部的DMA,而不是SOC上的通用DMA

PHY:用于将数据编码发送或者接收数据的器件。

RJ45:网络接口,比如常见的电口或者光口。

 

一个网卡就对应着一个struct netdev,如 4-1 所示。也就是说struct netdev是网卡硬件的抽象。netdev中有两个重要的功能,transmitreceive。当需要发送数据时,协议栈调用transmit方法通知驱动来进行数据发送,当网卡接收到数据时,系统通过receive方法接收数据并通知协议栈进行解析,如程序清单 4-1 网络协议栈网卡驱动的功能函数所示。

 

程序清单 4-1 网络协议栈网卡驱动的功能函数

struct  netdev_funcs    _G_cpswNetDevFunc[MAX_CPSW_INS*MAX_SLAVEPORT_PER_INST] = {

.init          = cpswInit,  /*  初始化网卡         */

.transmit      = __cpswIfCoreTx,  /*  发送函数           */

.receive       = __cpswIfCoreRx,  /*  接收函数           */

};

 

 

4-1  netdev结构体

 

当需要发送数据时,协议栈调用transmit方法通知驱动来进行数据发送。

数据经过两次拷贝由应用层复制到协议栈,再由协议栈复制到DMA缓冲区,然后设置网卡从DMA缓冲区中取数据发送协议栈中传递下来的pbuf中的数据缓冲区是带cache的,且物理地址是连续的,同时物理地址和虚拟地址也是一一对应的,发送流程如 4-2 所示:

 

 

 

 

 

4-2 发送数据流程

 

当网络发数据完成产生中断后,可以将耗时的处理放入网络异步处理作业队列t_netjob中进行排队处理,发送中断流程如 4-3 所示:

 

 

4-3 发送中断流程

当网络收发数据完成产生中断后,可以将耗时的处理放入网络异步处理作业队列t_netjob中进行排队处理。接收中断流程如 4-4 所示:

 

 

4-4 接收中断流程

当网卡接收到数据时,系统通过receive方法接收数据并通知协议栈进行解析。接收的时候,当网卡收到一帧数据后,将数据保存在DMA缓冲区中,然后通知cpu从缓冲区中取数据处理。cpuDMA缓冲区中的数据经过两次拷贝之后传送到应用程序。接收数据流程如 4-5 所示:

 

 

 

4-5 接收数据流程

 

接收因为网卡不知道何时回接收到数据,数据的到来。对于系统来说是一个异步事件,所以必须在驱动初始化的阶段就设置好各个缓冲区描述符中buffer的地址,这样当接收到数据的时候,网卡就将数据放到缓冲区描述符对应的buffer中。为 RX 描述符分配 PBUF 4-6 所示:

 

 

 

 

4-6  RX 描述符分配 PBUF

 

 

4.2 修改为字符设备网卡驱动代码

目的:将lwip协议栈的东西都(netdevpbuf)改掉实现字符设备操作函数(openclosereadwriteioctl)

 

使用__NGBE_MAC 来替代 netdev,使用__BUFF来替代 pbuf程序清单 4-2 所示:

 

程序清单 4-2 定义结构体和宏

 

#ifdef CFG_NET_CHAR_DEV

typedef struct {

    LW_DEV_HDR          devHdr;                         /*  设备头                      */

 

    UINT8               cDevNam[32];                      /*  设备名称                    */

    UINT8               hwaddr[8];                         /*  MAC 地址                   */

    UINT8               hwaddr_len;                        /*  MAC 地址字节数            */

 

    UINT32              iFlag;                             /* 文件标志                     */

    UINT32              iIndex;                            /* 设备号                       */

    UINT32              mtu;                              /*  MAC 帧最大长度             */

 

    LW_SEL_WAKEUPLIST   selWuList;                       /*  select 函数等待链           */

 

    volatile UINT32     DESC_pRxdataBase;                   /*  接收缓冲描述符数据指针      */

    volatile UINT32     DESC_pTxdataBase;                   /*  发送缓冲描述符数据指针      */

 

    LW_OBJECT_HANDLE    semMRdLock;                  /*  读数据互斥信号量            */

    LW_OBJECT_HANDLE    semBRdSync;                   /*  同步信号量                  */

    LW_OBJECT_HANDLE    semMWrLock;                  /*  写数据互斥信号量            */

 

    PVOID               priv;                            /*  CPSW PORTIF                 */

} __CPSW_MAC;

 

typedef __CPSW_MAC  *__PNGBE_MAC;

/*******************************************************************************************

  pbuf

*******************************************************************************************/

typedef struct {

 

    PVOID                payload;

 

    UINT16               len;

}__BUFF;

typedef __BUFF  *__P__BUFF;

 

/*******************************************************************************************

  协议栈的宏进行实现

*******************************************************************************************/

#define __P__BUFF_LEN_MAX   2048

 

#define ERR_ARG             PX_ERROR

#define ERR_OK  0

#define ERR_CONN            -11

#define ERR_VAL             -6

#define ERR_MEM             -1

#define err_t               CHAR

 

#define CDNS_BUF_ALIGN      4

#endif

 

 

注册字符网卡设备,将网络协议栈的代码注掉,实现字符网卡的设备创建和操作函数,初始化select等待链程序清单 4-3 所示:

 

程序清单 4-3

if (_G_iEthDrvNum == PX_ERROR) {

 

        _G_iEthDrvNum = iosDrvInstall(__cpswCharOpen, LW_NULL, __cpswCharOpen, __cpswCharClose,

                                      __cpswIfCoreRx, __cpswIfCoreTx, __cpswIfCoreIoctl);

        if (_G_iEthDrvNum == PX_ERROR) {

            return  (PX_ERROR);

        }

}

    _PrintFormat("####pCpswdev->cDevNam= %s_G_iEthDrvNum= %d\r\n",pCpswdev->cDevNam,_G_iEthDrvNum);

 

if (iosDevAddEx(&pCpswdev->devHdr, pCpswdev->cDevNam, _G_iEthDrvNum, DT_CHR) !=ERROR_NONE) {

        _PrintFormat("####iosDevAddEx error \r\n");

        return  (PX_ERROR);

}

 

SEL_WAKE_UP_LIST_INIT(&pCpswdev->selWuList);

 

 

 

openclose函数中主要就是实现了设备使用次数的计数,open函数根据标志判断是否设置非阻塞模式,程序清单 4-4 所示

 

程序清单 4-4

 

/*******************************************************************************************

** 函数名称: __cpswCharOpen

** 功能描述: 网卡打开

** 输 入  : pZYNQMac         设备

**           pcName           设备名称

**           iFlags           打开设备时使用的标志

**           iMode            打开的方式,保留

** 输 出  : PX_ERROR or ERROR_NONE

** 全局变量:

** 调用模块:

*******************************************************************************************/

#ifdef CFG_NET_CHAR_DEV

static LONG  __cpswCharOpen (__PNGBE_MAC pCpswMac, PCHAR  pcName, INT  iFlags, INT  iMode)

{

    if (pcName == LW_NULL) {

        _DebugHandle(__ERRORMESSAGE_LEVEL, "device name invalidate.\r\n");

        _ErrorHandle(ERROR_IO_NO_DEVICE_NAME_IN_PATH);

        return  (PX_ERROR);

 

    } else {

        if (iFlags & O_CREAT) {

            _ErrorHandle(ERROR_IO_FILE_EXIST);            /*  不能重复创建                */

            return  (PX_ERROR);

        }

        if (iFlags & O_NONBLOCK) {

            pCpswMac->iFlag |= O_NONBLOCK;               /*  非阻塞模式                  */

        }

 

        LW_DEV_INC_USE_COUNT(&pCpswMac->devHdr);

    }

 

    return  ((LONG)pCpswMac);

}

#endif

/*******************************************************************************************

** 函数名称: __cdnsCharClose

** 功能描述: 网卡关闭

** 输 入  : pDwMac         设备

** 输 出  : ERROR_NONE or PX_ERROR

** 全局变量:

** 调用模块:

*******************************************************************************************/

#ifdef CFG_NET_CHAR_DEV

static INT __cpswCharClose (__PNGBE_MAC pCpswMac)

{

    if (pCpswMac) {

        LW_DEV_DEC_USE_COUNT(&pCpswMac->devHdr);          /*  减少使用计数                */

    } else {

        return  (PX_ERROR);

    }

 

    return  (ERROR_NONE);

}

#endif

 

write函数复用了网卡设备的发送函数__cpswIfCoreTx,修改了函数入口,注释掉了网卡设备相关代码,根据字符设备驱动修改了发送数据部分代码,检查网络连接状态,若网络断开则返回错误,网络协议栈驱动的发送函数修改如 4-7 所示:

 

 



 

4-7 发送函数修改

发送函数在发送数据时,应用层将数据传递到驱动层,需要经过拷贝将数据复制到DMA缓冲区中,使用DMA空间来传数据。主要是因为CPU运算速度与内存读写速度不匹配CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。Cache的功能是:提高CPU数据输入输出的速率。

因为程序使用虚拟地址,而且一般使用Cache地址,所以虚拟地址中的内容与其物理地址上的内容不一定一致,所以在启动DMA传输之前一定要将该地址的CACHE刷新,即写入内存。

在进行DMA 操作时,如果没有对Cache 进行适当的操作,将可能产生以下两种错误:

1DMA 从外设读取数据到供处理器使用。DMA 将外部数据直接传到内存中,但cache 中仍然保留的是旧数据,这样处理器在访问数据时直接访问缓存将得到错误的数据。 

API_CacheInvalidate(DATA_CACHE,(PVOID)pBuf,(size_t)(PBUF_LEN_MAX+SIZEOF_STRUCT_PBUF));

 

2DMA 向外设写入由处理器提供的数据。处理器在处理数据时数据会先存放到cache 中,此时cache 中的数据有可能还没来得及写回到内存中的数据。如果这时DMA 直接从内存中取出数据传送到外设,外设将可能得到错误的数据。

API_CacheFlush(DATA_CACHE, (PVOID)pBuf, (size_t)SIZEOF_STRUCT_PBUF);

为了正确进行DMA 传输,必须进行必要的cache 操作。cache 操作主要分为 invalidate (作废)writeback (写回) ,有时也将两着放在一起使用。

 

申请DMA空间组成一个链表来供MAC控制器的描述符使用,如程序清单 4-5 所示:

程序清单 4-5

 

 

#ifdef CFG_NET_CHAR_DEV

struct luckDog

{

    void * dogNum;

    struct luckDog * next;

};

static struct luckDog* __TXHead ;

static struct luckDog* __TXTail ;

/*******************************************************************************************

** 函数名称: __cpsw_TxAlloc

** 功能描述: CPSW 发送描述符链表分配DMA内存

** 输 入  : pcpswInst      CPSW 结构体

** 输 出  : NONE

** 返  回  : NONE

*******************************************************************************************/

static VOID  __cpsw_TxAlloc (struct cpsw_inst  *pcpswInst)

{

 

    UINT Num = 203;

    struct luckDog*p,*q;

 

    p = (struct luckDog *)malloc(sizeof(struct luckDog));

 

    p->dogNum=API_VmmDmaAllocAlign(__P__BUFF_LEN_MAX,4);

    p->next = (struct luckDog *)malloc(sizeof(struct luckDog));

 

    q=p;

    __TXHead = p;

    p = p->next;

 

    while(Num)

    {

        p->dogNum=API_VmmDmaAllocAlign(__P__BUFF_LEN_MAX,4);

        p->next = (struct luckDog *)malloc(sizeof(struct luckDog));

        q=p;

        p=p->next;

        Num--;

    }

    __TXTail=q;

    __TXTail->next=__TXHead;//将链表头尾相连,实现链表循环。

 

}

#endif

 

 

网络协议栈驱动的接收中断函数修改,接收中断服务函数就是释放读同步信号量、唤醒读等待链表, 4-8 所示:

 

 

 

4-8 接收中断函数修改

read函数复用了网卡设备的接收函数__cpswIfCoreRx,修改了函数入口参数,注释掉了网卡设备相关代码,根据字符设备驱动修改了接收数据部分代码,增加了检查连接状态处理,通过读数据互斥锁实现并发对资源互斥访问,接收函数修改如 4-9 所示:

 

 

 



 


4-9 接收函数修改

 

 

ioctl函数来提供给应用层操作select的读非阻塞,获得MAC地址和设置MAC地址等,如程序清单 4-6所示:

程序清单 4-6

/*********************************************************************************************************

** 函数名称: __cdnsCharIoctl

** 功能描述: zynq 设备控制

** 输 入  : pZynqMac         设备

**           iCmd             控制命令

**           lArg             参数

** 输 出  : ERROR_NONE or PX_ERROR

** 全局变量:

** 调用模块:

*********************************************************************************************************/

#ifdef CFG_NET_CHAR_DEV

static INT __cpswIfCoreIoctl (__PNGBE_MAC  pCpswMac, INT  iCmd, LONG  lArg)

{

    __PNGBE_MAC pcpswwMac = pCpswMac;

    PLW_SEL_WAKEUPNODE  pselwunNode;

 

     switch (iCmd) {

 

     case FIOSELECT:

         pselwunNode = (PLW_SEL_WAKEUPNODE)lArg;

         SEL_WAKE_NODE_ADD(&pcpswwMac->selWuList, pselwunNode);

 

         switch (pselwunNode->SELWUN_seltypType) {

 

         case SELREAD:

         {

             API_SemaphoreMPend(pcpswwMac->semMRdLock, LW_OPTION_WAIT_INFINITE);

             if (__RxOWNER()) {                            /*  是否有接收到数据            */

                 SEL_WAKE_UP(pselwunNode);                /*  唤醒 select 等待链          */

             }

             API_SemaphoreMPost(pcpswwMac ->semMRdLock);

 

             break;

         }

 

         case SELWRITE:

 

             break;

         case SELEXCEPT:

 

             break;

         }

         break;

 

     case FIOUNSELECT:

         SEL_WAKE_NODE_DELETE(&pcpswwMac->selWuList, (PLW_SEL_WAKEUPNODE)lArg);

         break;

 

     case CPSW_MAC_SET:

         memcpy(_G_Cpsw_Mac_Set, (PUCHAR *) lArg, 6);

         __cpsw_mac_set(pcpswwMac, _G_Cpsw_Mac_Set);

         cpswInit (pCpswMac);

 

         break;

     case CPSW_MAC_GET:

         __cpsw_mac_get(pcpswwMac, _G_Cpsw_Mac_Get);

         lib_memcpy((PUCHAR *) lArg, _G_Cpsw_Mac_Get, 6);

 

         break;

 

     default:

         _ErrorHandle(ERROR_IO_UNKNOWN_REQUEST);

         return  (PX_ERROR);

     }

 

     return  (ERROR_NONE);

 }

#endif

 

下面主要对select接口读文件集处理流程概述如下:

1) select接口中会调用两次ioctl函数,第一次是执行FIOSELECT命令,第二次是执行FIOUNSELECT命令;

2) 在执行FIOSELECT命令时,首先会把唤醒节点加上到select等待链中,然后检查网络连接状态和接收描述符是否收到数据,如果连接正常并收到数据,直接唤醒等待链,如果连接断开或未收到数据,则执行下一个步骤;

3) 阻塞等待信号量释放,如果上一步直接唤醒等待链,则直接执行下一个步骤,否则会一直阻塞等待信号量释放。在中断服务程序中判断如果有接收中断产生,会唤醒select等待链。

4) 信号量释放后会再次调用ioctl函数执行FIOUNSELECT命令,删除加入的唤醒节点;

5) 更新读文件描述符集标志,退出select函数;

 

5. 编写测试程序

目的:测试网卡驱动基本功能。

 

测试程序大部分都是写好的只需要对其进行修改:

1) 发送方 IP 地址(板子) 与 接收方 IP(电脑)同网段。

2) 打开的网卡名需一致 可用shell命令 devs查看网卡驱动是否被加载。

3) 发送方 MAC 地址需与驱动层一致。

4) 打开设备,设备名要一致 (如:cpsw1)

5) 进行收发一次 ARP 报文

6) 收发四次 ICMP 报文

7) 关闭设备。

 

在发送ICMP包之前,会先调用arp发送arp地址解析协议,然后根据收到的arp的包得知主机的mac地址。然后再发送icmp包,因此在这里首先要看如何实现arp包的发送,Wireshark抓包如 5-1所示:

 

 5-1 Wireshark抓包

 

ARP包讲解:

 Wireshark抓包举例详细分析:以ARP发第一包为例,以太网目的地址:(ff:ff:ff:ff:ff:ff)为广播包,以太网源地址:(020203040505),网络字节序为大端对齐,帧类型:0x0806,硬件类型:1,协议类型:IPv4,硬件地址长度:6,协议地址长度:4,请求次数:1,发送者硬件地址:(020203040505),发送者IP地址:192.168.1.236,目标硬件地址:(000000000000),目标IP地址(192.168.1.10)。如 5-2 所示:

 

 



 

 5-2 ARP分析

6. 调试代码

目的:找BUG

如果出现接收加打印,造成接收无中断产生,如 6-1所示

 

 



6-1 无接收中断

出现此种情况,第一是因为加了打印,正常情况下,网卡的收发速度都是1ms以下的,在字符网卡中,收发没有像协议栈网卡将读取报文函数 放入了网络异步处理作业队列中一样,字符网卡读取报文应用层是用while循环来读取,并且使用了select唤醒读等待链。所以,当系统在接收数据时因为打印耗费时间片,产生问题。

 

记录 pBuf 长度

果记录的 pBuf 长度小于60,即描述符缓冲区偏移量长度小于60网卡将不发送报文,最大帧大小2016字节(VLAN 2020)在看芯片手册,网卡中会有提到发送报文的最小长度和最大长度。如 6-2 所示:

 

6-2 最大帧大小

加入检测是否连接成功是在热插拔函数中,经过暴力拔插网线,发现重插网线并没有发生重新连接成功提示,于是查看热插拔任务优先级和测试字符网卡任务优先级,发现测试字符网卡任务的优先级为150,热插拔任务的优先级为250,所以热插拔任务是在运行测试字符网卡任务时,不会被调用的。最终对测试字符网卡任务进行优先级的修改即可解决,如 6-3 所示:

 



 

6-3 热插拔任务和测试任务

在接收函数中使用while,将出现卡住,是因为接收描述符是循环链表,如果对OWNER位进行判断,当MAC控制器向描述符写入报文之后,又向下一个写入报文,当读取时,会出现驱动层把两个报文都读出来了,可却没有将报文信息传递给应用层,就将数据覆盖掉了,产生数据的丢失,如 6-4 所示:

 

6-4 接收函数使用while出现问题示例

 

设置MAC地址Mac地址就是在媒体接入层上使用的地址,通俗点说就是网卡的物理地址,现在的Mac地址一般都采用6字节48bit

此程序出现寄存器MAC地址无法修改的问题如 6-5所示,于是就使用修改驱动的存储MAC的结构体。都是可以的,Mac地址是保存在网卡的EPROM里面,通过网卡生产厂家提供的修改程序可以更改存储器里的地址,即使网卡没有这样的设置我们也可以通过间接的方法修改,一般网卡发出的包的源Mac地址并不是网卡本身写上去的,而是应用程序提供的,只是在通常的实现中,应用程序先从网卡上得到Mac地址,每次发送的时候都用这个Mac做为源Mac而已。

 

6-5 寄存器MAC地址无法修改示例

7. 总结

在进行字符网卡移植的时候,必须要将带网络协议栈的网卡代码看透了,描述符的运行机制和内部的结构是怎么样的,这个是一定要清楚的,因为这个是网卡发送和接受函数的数据结构的核心。这个sync的网卡代码的描述符就很特别,还会将描述符循环链表打断,在进行复原处理,看了寄存器的讲解就知道是因为要搭配寄存器的各个功能配置。

细心细心再细心!!!

8. 参考资料

字符设备网卡移植网讯网卡移植字符设备网卡驱动笔记



    • Related Articles

    • Qt 应用程序移植到 SylixOS

      Q:Qt 应用程序移植到 SylixOS 需要多大的工作量? SylixOS 对 Qt 接口的支持是全功能支持的,只需要在SylixOS系统下重新编译就可以了,基本没有工作量。
    • VxWorks 程序到 SylixOS 之移植指南

      一般来说,VxWorks程序又可分为内核程序和应用程序,内核程序又可分为BSP程序和内核模块程序。 本文重点阐述内核程序的移植。 SylixOS如何操作硬件: VxWorks如何操作硬件: 1、工程的选择 VxWorks 工程类型: SylixOS 工程类型: 2、VxWorksBSP中自定义函数被外部模块使用,在SylixOS中需要增加到符号表中,可以通过API_SymbolAddStatic或者LW_SYMBOL宏修饰方式增加。 ...
    • Linux工程移植笔记-基于arping案例

      1. arping简介 arp(Address Resultion Protocol)地址解析协议,是通过解析网络层地址来找寻数据链路层地址。 ...
    • libamp_virnetdrv 虚拟网卡在 T3 AMP 上的移植案例

      libamp_virnetdrv 可以方便的移植到各种平台,为 AMP 架构提供简单、高效的核间通信机制。下面以 T3 AMP(SylixOS + SylixOS)为例介绍具体的移植过程。 1、驱动支持 虚拟网卡包含 libamp_virnetdrv与 libamp_virnetdrv_config 两个部分。T3 是 ARM 平台,因此首先拷贝 libamp_virnetdrv\src\demo\arm_aarch64 下的四个文件到 T3 每个系统的 BSP 目录 ...
    • IDE 中如何屏蔽单个工程文件

      问:IDE 中如何屏蔽单个工程文件? 在工程中右键选择 Properties 会弹出如图所示属性库,在 SylixOS Pro 选项卡中禁能所有架构的选项,即可达到暂时屏蔽单个工程或工程文件夹的作用。  该方法的好处在于: (1 )移植中间件,保留中间件源码工程样式并且又屏蔽不需要的工程; (2)移植中间件过程可以暂时屏蔽编译不通过想要裁剪的工程,如果选择的是自定义 Makefile 模式,需要删除文件或删除 Makefile 中的 sourcelist,并且人工记录,从而造成 SylixOS ...