一步步讲解如何调试vxworks网卡驱动-适合嵌入式初学者

来源:互联网 发布:淘宝机器人在哪 编辑:程序博客网 时间:2024/05/01 13:11
http://xiyong8260.blog.163.com/blog/
注:本文为原创,如有转载,请注明出处,谢谢合作。
引言:
前一段时间在21ic上发了一个帖子,关于调试LAN91C111 vxworks网卡驱动的碰到的问题的,后来调试完了之后,一直想把这个调试过程写出来,供大家参考。不当之处也欢迎大家指出。

一.概述
根据项目需求,需要设计一个嵌入式数据转发板,CPU读取FPGA的采集到的数据,然后通过网络进行转发,使用TCP协议,最终的转发速率要求在12Mbps以上。另外,该设计需要满足工业级温度要求。操作系统使用vxworks。
以前设计的BCNG2440开发板,采用的是DM9000网卡芯片,DM9000网卡芯片是商业级的温度范围,因此,这次使用了SMC公司的LAN91C111芯片。与DM9000一样,LAN91C111也是一个10M/100M自适应的网卡芯片,然而,功能比DM9000多,内存管理比DM9000复杂,编程涉及的东西也多。更重要的是,LAN91C111使用的人较少,网络资料也较少,开发难度因此大了许多。
电路连接上,LAN91C111使用S3C2440的nGCS4片选线,基地址为0x20000000,中断号为EINT4,32位的数据线,不使用EEPROM,使用内部PHY。
有过调试vxworks下DM9000、8260网卡驱动的经验,也有过linux下调试网卡的经验,自觉得调试应该会比较顺利的。

调试的基本步骤是:1)寄存器基本读写调试(配合硬件调试)2)网卡寄存器配置与初始化调试3)基本的发送调试4)基本的接收调试5)中断调试6)和操作系统结合起来调试7)应用程序测试。

然而,好事多磨,原计划一个月的调试托了两个多月才调试完,历经了些曲折,利用假期把他些出来,希望对大家有所帮助,尤其是对于嵌入式的初学者。

从这个调试记录中可以看到,嵌入式的调试不但需要细致细心,而且需要毅力恒心,只要坚持下去,总是能够找到办法的。我想做任何事情都是如此。

二.    设计代码
从smc公司网站上下载的vxworks驱动代码为x86平台的,而我们的CPU为S3C2440需要进行代码移植。
代码修改总结如下:
1)添加修改底层的IO读写函数
/*xiyong add the lower layer read and write function*/
#define    sysInByte(port)     (*((volatile char *)(port)))
#define    sysInWord(port)     (*((volatile short *)(port)))
#define    sysInLong(port)     (*((volatile long *)(port)))
#define    sysOutByte(addr,value)    (*((volatile char *)(addr)) = value)
#define    sysOutWord(addr,value)    (*((volatile short *)(addr)) = value)
#define    sysOutLong(addr,value)    (*((volatile long *)(addr)) = value)

2)添加对cpu的内存bank设置
   /*xiyong add cpu memory bank control*/
   BWSCON=0x221a99a2;
   BANKCON4=((0<<13)+(1<<11)+(3<<8)+(0<<6)+(0<<4)+(3<<2)+0);

3)添加对CPU中断和管腿的设置
void cpuForLAN911Init(void)
{    
     rGPFCON &= 0xfeff;  /* set pin GPF4 as EINT4 [9:8]=10*/
    rGPFCON |= 0x200;    
    /* high level triggered, [18..16]=001 high level*/
    rEXTINT0 &= 0xfff9ffff;
    rEXTINT0 |= 0x10000;
    
    rSRCPND = (rSRCPND|(1<<4));/*clear status for int4-7*/ 
    rEINTPEND =( rEINTPEND|(1<<4));/*clear status for int4*/        
    rINTPND = (rINTPND|(1<<4));/*clear status for int4-7*/  

    rEINTMASK=(rEINTMASK&(~( 1<<4) ));   /*enable eint4*/    
}

STATUS lan91c111Start
    (
    END_OBJ * pEnd    /* device ID */
    )
    {
    LAN91C111END_DEVICE      *pDrvCtrl = (LAN91C111END_DEVICE *)pEnd;

       /*xiyong add here, for interrupt*/
       cpuForLAN911Init();
       SYS_INT_ENABLE (pDrvCtrl);

4)修改几个函数的ushort类型为ulong类型
UINT ReadPhyRegister( USHORT IOBase, UCHAR  phyaddr, UCHAR  phyreg);
VOID WritePhyRegister( USHORT IOBase, UCHAR  phyaddr, UCHAR  phyreg, ULONG  phyregdat );
LOCAL USHORT InputMDO( USHORT IOAddress );
LOCAL VOID OutputMDO( USHORT IOAddress, UCHAR lev );
LOCAL UINT DetectPHY(LAN91C111END_DEVICE *Adapter ,USHORT IOBase);

5)修改confignet.h中的load参数:
#define SMC911_LOAD_STRING  "0x20000300:0x4:0x4:0x2:0x3100"

6)修改中断宏定义
/*xiyong modify here*/
#define SYS_INT_ENABLE(pDrvCtrl) { intEnable(pDrvCtrl->ilevel);}
#define SYS_INT_DISABLE(pDrvCtrl) { intDisable(pDrvCtrl->ilevel);}

#define LAN91C111_DEV_NAME       "smc"  /*xiyong modify here*/

8)修改发送中断,屏蔽netjobadd函数
//if(pDrvCtrl->pTxReadIndex->pMblk != NULL)
//              netJobAdd ((FUNCPTR)lan91c111Send,(int)pDrvCtrl,0,0,0,0);

9)修改smc.h
定义
TX_PACKET 0x20
RX_PACKET 0x20

10)syslib.c添加对LAN91C111的内存映射,采用的是实地址映射
…..    
{
        (void *) 0x20000000,        /* LAN91C111  */
        (void *) 0x20000000,
        ROUND_UP (SZ_4M, PAGE_SIZE),
        VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE | VM_STATE_MASK_CACHEABLE,
        VM_STATE_VALID    | VM_STATE_WRITABLE     | VM_STATE_CACHEABLE_NOT
    },
……
三.    调试记录
1.下载源代码
老实说,我调试了很多的驱动程序,没有一个是自己重头编写的。有两种途径,从芯片的官方网站上下载提供的代码,然后根据自己的硬件修改,因此,工作量的多少就取决与自己的硬件和官方硬件的差别多大了;还有些芯片,比较新,没有提供vxworks代码,但是linux是开源的,任何一款新的芯片出来,几乎立刻就会有linux的驱动,因此,可以下载linux的代码,然后移植到vxworks中,这也是我惯用的方法。一直认为,linux是嵌入式之源。
从smc公司网站上下载的vxworks驱动代码为x86平台的,而我们的CPU为S3C2440需要进行代码移植。

2、硬件和寄存器读写调试
预计两三天完成此步骤,实际用了一周多。
一般来说,每个芯片都有一个ID号,寄存器测试就是看是否能够将此ID号读出来,如果能够读出来,说明硬件的基本连接是正确的。
如果没有读出来,可能的原因有:1)硬件设计有问题,连接不正确2)器件有问题,买到了坏的芯片3)焊接的原因,例如虚焊,或短路4)软件的原因,例如读写时序配置不正确。
所以如果ID号读不出来,是相当麻烦的事情,因为各种原因都有可能,这也是嵌入式调试的难点所在,需要软硬件都非常的熟悉才行。
在设计硬件的时候,参考了网络上一些DSP和LAN91C111的电路图,基本上都是16位的,而我们为了加快访问速度,使用了32位的接口,没有现成的例子,只能自己推理了,设计的时候,觉得应该没有问题了。而当调试出了问题的时候,就开始怀疑自己的设计是否正确。
1)硬件设计
下面,我们来看看LAN91C111 datasheet手册。
LAN91C111芯片颇为复杂,与CPU的接口,可以同步传输,也可以异步传输,可以以8/16/32位的方式访问,手册上给出了三种总线接口例子,有两个是是用于同步传输的(使用了LCLK的时钟信号),还有一个是16位的ISA总结接口的,我们的设计与此类似。
各个信号的管腿接法如下:
LAN91C111      CPU S3C2440
A1             (悬空)
A2-A15     接CPU的A2-A15
解释一下地址线的接法,为什么这里A1是悬空的?一般来说,地址线都是从A0开始的,我们通常来说,每个内存多大,都是指字节大小,也就是8位的;如果以16位,word为单位,那么就从A1开始算起,如果以32位为单位,就从A2算起了,因此A0和A1就不使用了,而LAN91C111是没有A0管腿的,因此A1悬空。
D0-D31     接CPU的D0-D31
我们使用32位的,正好可以一一对应上。
这里,有两个问题需要说明一些。1)有些CPU,例如Motorola的PowerPC,它的CPU的地址线和数据线的顺序是反的,需要反着接,也就是CPU的D0接网卡的D31。
2)我们现在是以双字(32字节)为单位进行访问,地址线A0,A1是没有使用的,那就存在一个问题,是否能够只访问32位的其中某个字节或者某两个字节呢?这就需要通过字节选择管腿来实现,nBE0-nBE3就是完成这个任务的。
nBE0-nBE3        接CPU的WBE0-WBE3
nRD        接CPU的nOE
nWR        接CPU的new
AEN            接CPU的nGCS4
一般来说,异步内存读写基本、最重要的几根线就是数据线、地址线、读写控制线以及片选线。CPU对外围设备的读写控制时序,是学习嵌入式系统需要掌握的基本概念之一。对于实验室新进入的同学,我通常让他们使用FPGA/CPLD编写一段VHDL代码,实现CPU对FPGA的异步总线读写,以加深对概念的理解。
读写控制线还需要说明一些,有些芯片是具有单独的读使能和写使能控制线,而有些芯片的读写控制线是一根线,例如R/W线,高电平是读,而低电平写;现在很多的CPU都同时具备两种机制。
ARDY            接CPU的nWAIT
额外的补充,ARDY信号,一般来说,CPU的速度比较快,而外围设备的速度比较慢,CPU的内存控制寄存器可以设置等待多长时间,以获得外设的数据;如果外设数据准备的时间比较长,可以使用这个信号,通知CPU见到这个信号的时候才可以读写外设的数据。
INTR0            接CPU的EINT4
中断信号,需要注意的是这是高电平有效的中断,一般来说,外设的中断都是低电平有效的,所以软件编程的时候需要额外注意。
RESET            CPU的nRESET通过反向得到
这又是不一样的地方,通常芯片都是低电平复位,而这个芯片是高电平复位,因此,需要把CPU的复位反向一下得到?我也搞不清楚这个芯片为什么总是这么多的不一样:)
不使用的几个管腿的处理
nCYCLE、W/nR 、nRDYRTN、 LCLK            接VCC
nADS            接GND
还有几个信号悬空即可。
这些信号是不使用的,参考数据手册的建议,有些接高电平处理,有些接低电平处理。
这是一个值得注意的细节,并不是不使用的管腿不管他即可,为了防止干扰或误触发,特别是输入管腿,是需要上拉活着下拉的,数据手册一般会有建议的。
以上是基本的与CPU接口相关的信号,还有其他的信号。例如与以太网变压器的接口信号、时钟信号、电源信号等等。
对于时钟信号,一般都提供两种接口,有源25M时钟,或者无源晶体,本设计中使用了有源时钟,从XTAL1输入,XTAL2悬空;如果使用两腿的无源晶体,就直接接上XTAL1和XTAL2即可。
还有就是EEPROM,网卡芯片一般都可以接一个93C56之类的EEPROM信号,用于基本信息的保存,例如MAC地址之类。但是也可以不使用,这些基本信息可以在初始化网卡的时候直接在程序中给出。嵌入式系统设计往往不使用这个芯片(可以节约成本啊),需要将ENEEP信号接低电平,告诉LAN91C111不使用EEPROM。另外,还有几个IOS0-IOS1也接低电平,告诉LAN91C111使用默认的地址空间。


2)软件设计
下面再回到如何读取网卡的ID号?软件如何编写。
LAN91C111的内存分为IO空间和内部RAM空间,IO空间主要是用来对寄存器访问的,而内部SRAM空间是发送和接收数据的暂时存放区域。IO空间可以直接访问,而内部SRAM空间是通过位于IO空间的指针访问的。
LAN91C111只设置了16个字节的IO空间,每个IO空间的寄存器都是16位的,这样,实际上只够放8个寄存器,而实际上,LAN91C111有将近30个16位的寄存器,怎么办呢?通过BANK来解决,设置4个BANK,每个BANK都是16字节,因此,访问某个寄存器是先选择BANK,然后再选择偏移量。某个BANK的最高位都是BANK选择寄存器,因此,不管如何设置,总是能够访问到BANK选择寄存器。
BANK选择寄存器为16位,而实际上只要3个bit就可以访问8个bank了(虽然现在为4个,但是内部扩展了几个,我们没有使用),岂不是浪费了很多bit?确实是这样,为了方便调试,该芯片将高8位固定为0x33,因此,只要读出BANK选择寄存器高8位为0x33,就可以认为能够正确读出网卡寄存器了。当然,这个芯片的确是有ID寄存器的,位于BANK3的C地址。但是读取BANK选择寄存器更为容易,就以它来替代了。
我们已经知道,这个网卡使用了nGCS4作为片选,nGCS4在S3C2440的基地址为0x20000000,如何读出ID号呢?那就要读出IO空间的地址。IO的空间地址也是有偏移量的,可以通过EEPROM和IOS0-IOS2管腿配置,在本设计中,采用默认配置,偏移量为0x300。因此,只要以WORD的方式访问0x2000030E空间,获取其高8位,就应该读出0x33的。
至少可以使用三种方式读取BANK选择寄存器的值。
1)    在uboot中使用md查看内存的命令读出;
2)    在ADS(AXD)环境下编写C语言通过仿真器下载调试运行;
3)    在vxworks tornado环境下编写C语言下载调试运行;我们的板子设计了两个网卡,一个是DM9000,已经调试通过了,因此,可以运行tornado来调试,不需要仿真器了。
用C语言编写如下:
unsignd short BANKUP;
BANKUP =((*(unsignd short *)(0x2000030E))&0xff00)>>8;
printf(“BANK SELECT UPPER is %x ”, BANKUP);

这里有必要谈到C语言。嵌入式开发,C语言是必须的,绝大部分代码使用C编写,还有少量代码是用汇编实现,例如启动代码。C语言的最大好处是对内存的访问,使用指针就可以直接访问内存了。
满怀希望怀着激动的心情将程序下载运行,希望能够看到打印出来的0x33,却发现显示的是0xff,没有读出来。前面已经说过,这表明了若干种可能的情况,是调试中一开始最不想遇到的问题,然而,必须调试下去。
    
原创粉丝点击