uClinux下移植Ne2000兼容的网卡驱动程序

我是linux的新手,可以说从来没有在linux下写过程序,对于linux内核也是相当陌生,
前一段时间,拿着tpu一个移植好了的uClinux在S3C44B0(ARM7TDMI核的嵌入式处理器)
上的版本,把它成功的跑在了我自己的S3C44B0的板子上,这也就算是平生在uClinux下
作的第一个工作吧。接下来就是添加网卡驱动,我用的是RTL8019AS--比较常用的ISA接
口的以太网芯片。下面就从一个新手的角度来说说我的移植过程吧,其实很简单,我的
整个摸索+移植的过程也就花了2天的时间,我尽量写的详细(罗嗦?)一点,希望对像
我这样的新手入门有所帮助,错误之处在所难免,欢迎指正。

开始的时候,我也是摸不着头脑,不知道该从什么地方入手。用SoureInsight把整个
uClinux内核的源码都添加进来,熟悉一下linux的内核(其实就是在里面瞎撞,也不怎么
能看懂)。按照linux内核目录的分类,很自然的就找到Ne2000网卡的驱动就是
./drivers/net/ne.c,和它相关的还有8390.h和8390.c。看看代码,逐渐的就明白了:

首先,在Ne.c中函数ne_probe就是网卡的检测函数,如果检测到Ne2000兼容的网卡
就return0。那个函数没有什么具体的工作,就是搭了一个架子。看的有前人在这个函数
开始写到:
#ifdefined(CONFIG_NETtel)&&defined(CONFIG_M5307)
…………
#elifdefined(CONFIG_COLDFIRE)
staticintonce=0;
if(once)
return-ENXIO;
if(base_addr==0){
dev->base_addr=base_addr=NE2000_ADDR;
dev->irq=NE2000_IRQ_VECTOR;
once++;
}
#endif
就明白了,可以把网卡的基地址、中断号都放到这里面定义。我也跟着照葫芦画瓢,添
加了一个:
#elifdefined(CONFIG_ARCH_S3C44B0)//--bythreewater
staticintonce=0;
if(once)
return-ENXIO;
if(base_addr==0){
dev->base_addr=base_addr=ARM_NE2000_BASE;
dev->irq=ARM_NE2000_IRQ;
once++;
}
其中:ARM_NE2000_BASE和ARM_NE2000_IRQ是在配置内核的时候定义的,这个以后再说。

接下来,具体的工作就转移到了ne_probe1函数里面做。用SourceInight跟进来看(这个
软件太好用了,忍不住在这里再坐一会广告)。Ne_probe1中,一开始就是
reg0=inb_p(ioaddr);
if(reg0==0xFF){
ret=-ENODEV;
gotoerr_out;
}
很容易理解,就是读一下网卡的基地址,对我来说也就是RTL8019的REG0,如果是0xff,
说明没有检测到网卡,返回错误。好了,在下面添加一行
printk("beginfindNe2000NetCard...\tbaseaddress=0x%X\n",ioaddr);
//--addbythreewater
来证明我们的想法是正确的,程序如果能读取8019的REG0,就应该显示出这一行。可是
,那个ne_probe是谁调用的呢?还是用SourceInsight去找,用jamptocaller,哈哈,
太容易了,立刻就看到了,网卡的检测是从./drivers/net/Space.c的ethif_probe函数
中实现的,关键代码:
if(probe_list(dev,eisa_probes)==0)
return0;
eisa_probes在前面定义成全局:
staticstructdevprobeeisa_probes[]__initdata={
#ifdefCONFIG_DE4X5/*DECDE425,DE434,DE435adapters*/
{de4x5_probe,0},
#endif
…………
{NULL,0},
};
我也照着添加了:
if(probe_list(dev,arm_probes)==0)
return0;
并定义:
staticstructdevprobearm_probes[]__initdata={
#ifdefCONFIG_ARM
{ne_probe,0},
#endif
{NULL,0},
};
这样,编译内核启动,果然,显示出了输出结果。

继续分析修改ne.c中ne_probe1的代码(关键的东东全在这里面呢)。接下来就是
outb_p(E8390_NODMA+E8390_PAGE1+E8390_STOP,ioaddr+E8390_CMD);
regd=inb_p(ioaddr+0x0d);
outb_p(0xff,ioaddr+0x0d);
读取REGD中的数据,这里,再仔细跟踪一下outb_p这个函数,在x86中,这个就是一个IO
口的输出函数,在S3C44B0是存储器和IO统一编址的(或者说不分存储器还是IO),经过
了几次宏定义以后,很快找到如下宏代码:
(*(volatileunsignedchar*)(a))
和我想的一样,就是靠这个访问外部总线的。我的8019在S3C44B0的Bank5上,工作在跳
线模式,算了一下,起始基地址就是0x0a000600。

这里,需要说明一下我的硬件配置和连接,8019工作在16位模式下,S3C44B0的Bank5配
置成16位模式,数据线一对一的连接,地址线错开一位--8019的A0连接S3C44B0的A1……
这样,8019的基地址(Reg0的地址)是0x0a000600,Reg1的地址就是0x0a000602……地
址不是连续增加的,所以,对应的驱动程序要做相应的修改。查找E8390_CMD的定义,发
现,在8390.h中有:

#defineE8390_CMDEI_SHIFT(0x00)/*Thecommandregister(forallpages)
*/
/*Page0registeroffsets.*/
#defineEN0_CLDALOEI_SHIFT(0x01)/*Lowbyteofcurrentlocaldmaaddr
RD*/
#defineEN0_STARTPGEI_SHIFT(0x01)/*StartingpageofringbfrWR*/
……
而EI_SHIFT根据不同的配置有两种定义,如下:

#ifdefined(CONFIG_MAC)||defined(CONFIG_AMIGA_PCMCIA)||\
defined(CONFIG_ARIADNE2)||defined(CONFIG_ARIADNE2_MODULE)||\
defined(CONFIG_HYDRA)||defined(CONFIG_HYDRA_MODULE)||\
defined(CONFIG_ARM_ETHERH)||defined(CONFIG_ARM_ETHERH_MODULE)
#defineEI_SHIFT(x)(ei_local->reg_offset[x])
#else
#defineEI_SHIFT(x)(x)
#endif

看来,在8390的驱动中已经考虑到了不连续增长的地址的问题了,继续跟踪查看ei_loca
l->regoffset[x]的定义就比较麻烦了。干脆,我用一个笨方法,直接添加:

#elifdefined(CONFIG_ARM)||defined(CONFIG_ARM_MODULE)//--by
threewater
#defineEI_SHIFT(x)((x)*2)

对应的,在ne.c也有类似的定义问题:
#defineNE_CMD0x00
#defineNE_DATAPORT0x10/*NatSemi-definedportwindowoffset.*/
#defineNE_RESET0x1f/*Issueareadtoreset,awritetoclear.*/
#defineNE_IO_EXTENT0x20
添加成:
#ifdefCONFIG_ARM//--bythreewater
#defineNE_CMD0x00
#defineNE_DATAPORT0x20/*NatSemi-definedportwindowoffset.*/
#defineNE_RESET0x3e/*Issueareadtoreset,awritetoclear.*/
#defineNE_IO_EXTENT0x40
#else
……
这样,地址偏移的问题就基本解决了。当然,在Ne.c中,也有直接访问reg的代码,比如
上面说的代码也相应的添加成:

#ifdefCONFIG_ARM//--addbythreewater
regd=inb_p(ioaddr+0x0d*2);
outb_p(0xff,ioaddr+0x0d*2);
#else
regd=inb_p(ioaddr+0x0d);
outb_p(0xff,ioaddr+0x0d);

我没有看过linux编程的规范,也不知的修改内核有什么规矩,不过,我都是用预处理来
添加我自己的代码,从来不直接在原有的代码上修改,我觉得这样更可以保证代码的完
整性和可移植性,而且,还容易比较,容易找出问题(当然,如果#if嵌套多了,也很难
看的:()。

接下来的初始化8019,就没有什么问题了,然后就是配置网卡的物理地址了。在我的系
统上,没有使用8019的初始化配置芯片,物理地址需要在程序中直接写入(其实,就是
使用配置芯片,也需要用程序读出再写入的),物理地址可以编译到代码里,也可以存
储到flash的一个固定地址中。可以参考ne_probe1里面的例子,照着勒就可以了。剩下
注册中断什么的,也就是算好了中断号,照着添加自己的代码。很容易的。

到这里,似乎就没有什么工作了。编译内核,启动,恩Ne2000兼容的网卡找到了,接下
来就不正常了。系统报告,反复陷入那个网卡的中断……

反复陷入中断,很容易想到就是中断模式配置的问题,8019的中断是高电平有效,看看S
3C44B0上的配置,果然不对。这个配置是在Bootloader中做好了的,改一下,就好了。
我把他改成了上升沿触发。

别人的批注:
最好改为高电平触发,我就吃过这样的苦头,
当时我那个驱动不太稳定,一旦有错误,就死活不工作,
后来发现是8019的中断线一直为高,显示有中断,
但CPU不知什么原因,开始的时候没有检测到上升沿,因此以后就再也收不到中断了,
把触发方式改了以后,就非常稳定了


另外,因为S3C44B0是IO空间和存储器统一编址。这就容易忽视一个问题,就是缓冲。对
于存储器,加上片内的缓冲可以提高效率,不过对于外部设备比如这个8019,就不能使
用缓冲。记住,缓冲的范围一定要配置正确,我开始就弄错了,产生了一些莫名其妙的
问题,耽误了不少时间。

上述问题都解决了,启动的时候可以找到网卡,可以配置好物理地址,启动以后ifconfi
geth0也没有问题,这次应该没有问题了吧。可是,结果还是ping通。这次就比较麻烦
了,没有内核耕种调试的手段,只能靠printk来输出?不知道应该从哪里入手了。不过
还好,调试以太网,有Sniffer(一个功能强大的抓包软件,在局域网内的数据包都是抓
到)。就靠它了,在我的PC上运行,抓包。在uClinux下ping主机的IP。果然能抓到数据
包。分析抓取的数据包发现问题。

按理说,ping的时候,第一次不知道目的主机的Mac地址,所以,应该发送ARP广播,发
送的数据大概的格式开头应该是FFFFFFFFFFFFAABBCCDDEEFF…………(AA
BBCCDDEEFF表示发送方的Mac地址),可是我抓到的数据包是FFFFFFFFFFFF
AAAABBBBCCCCDDDDEEEEFFFF……看明白了,这个问题应该是网卡发送的时
候,向网卡写入数据连续写了两次。这个问题最容易让人想到是S3C44B0的挂8019的那个
Bank的数据宽度配置错了。可是,我仔细的看了,不是这个问题。那就只有再仔细看看
源码了。还是在drivers/net/ne.c里面,ne_block_output函数--这个就是8019发送时候
调用的函数了,里面有代码:

if(ei_status.word16){
outsw(NE_BASE+NE_DATAPORT,(void*)buf,count>>1);
}else{
outsb(NE_BASE+NE_DATAPORT,(void*)buf,count);
}

我跟踪了一下,ei_status.word16=1,这个没有问题。那么,问题就出在outsw函数上了
。用SourceInsight一层层的跟踪(做一个函数右一个宏的,定义的可真多,好多不同模
式或者处理器下的相同定义,要看清楚自己的),最后,终于把目标锁定在了arch/armn
ommu/lib/io-writesw-armv3.S和io-writesw-armv4.S两个汇编文件。到底是哪个呢?

熟悉ARM家族的人应该知道ARMv3和ARMv4的一些区别,看看这两汇编,就可以开出来他们
对16位数读写操作的不同,按照道理S3C44B0应该是ARMv4(我记得应该是,不到出处了
,至少看了那个两汇编文件,我认定应该用ARMv4那个),可是,看了一下便一输出的.o
文件,是io-writesw-armv3.o,显然弄错了,这里就是问题了。那么为什么要编译ARMv3
而不是ARMv4这个文件呢?在Makefile和Config.in中经过一番寻找,终于找,原来在定
义arch/armnommu/config.in中,定义CONFIG_ARCH_S3C44B0的时候,没有定义

CONFIG_CPU_32v4

那么,默认情况下,就定义CONFIG_CPU_32v3,用它来编译。好了。把ARMv4的定义添上
。顺便把前面说的ARM_NE2000_BASE和ARM_NE2000_IRQ的定义以添加到这里,让用户可以
自己定义裁剪。

hex'BaseAddressforNE2000ethernet'ARM_NE2000_BASEyouraddr
hex'IRQforNE2000ethernet'ARM_NE2000_IRQyourinterrupt

好了,编译通过。运行,果然没有问题了。Ping可以,telnet可以,在内核中把NFS打开
,mount-tnfs……也好用。哈哈。太好了。至此,8019在S344B0组成的uclinux平台
上的驱动,移植成功。相信其他的网卡芯片移植驱动程序应该也基本是这个思路。现在
写出来与大家共享。希望对新手入门有所帮助,同时文章中有我理解错误的地方,也希
望高手指教。

最后,再总结一下,移植过程中需要注意的几个问题:

1、确定网卡的基地址、中断无误
2、注意网卡的数据总线宽度,地址是否连续,如果不连续,如何映射
3、注意网卡的中断的模式和处理对应的外部中断是不是一致
4、对于IO和RAM统一编址的处理器,注意缓冲区范围的设置
5、注意ARMv3和ARMv4等一些和处理器结构相关的底层函数库带来的问题
6、用抓包软件可以帮助分析定位问题所在

Btw,我的PC平台是在WindowsXP+VirtualPC下安装的RedHatlinux8.0,我觉得这样
调试起来比较方便,可以用SoureInsigh来阅读,编写代码,可以在Linux编译。充分发
挥两个操作系统的优势。很适合于像我这样的,不熟悉Linux人开发

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值