三水

网志 | 三水's blog

2005-12-7 7:17:00
uClinux下移植Ne2000兼容的网卡驱动程序——还是老文章

还是2.5年前写的老文章,拿了过来凑数了。这两天在写一个书稿(关于嵌入式Linux驱动程序和相关硬件的),每天敲键盘,敲得手疼,这里的更新就先吃老本了...

 

uClinux下移植Ne2000兼容的网卡驱动程序(1)
我是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兼容的网卡就return 0。那个函数没有什么具体的工作,就是搭了一个架子。看的有前人在这个函数开始写到:
#if defined (CONFIG_NETtel) && defined (CONFIG_M5307)
…………
#elif defined(CONFIG_COLDFIRE)
 static int once = 0;
 if (once)
  return -ENXIO;
 if (base_addr == 0) {
  dev->base_addr = base_addr = NE2000_ADDR;
  dev->irq = NE2000_IRQ_VECTOR;
  once++;
 }
#endif
就明白了,可以把网卡的基地址、中断号都放到这里面定义。我也跟着照葫芦画瓢,添加了一个:
#elif defined(CONFIG_ARCH_S3C44B0) //--by threewater
 static int once = 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;
  goto err_out;
 }
很容易理解,就是读一下网卡的基地址,对我来说也就是RTL8019的REG0,如果是0xff,说明没有检测到网卡,返回错误。好了,在下面添加一行
 printk("begin find Ne2000 Net Card...\tbase address=0x%X\n",ioaddr);
 //--add by threewater
来证明我们的想法是正确的,程序如果能读取8019的REG0,就应该显示出这一行。可是,那个ne_probe是谁调用的呢?还是用SourceInsight去找,用jamp to caller,哈哈,太容易了,立刻就看到了,网卡的检测是从./drivers/net/Space.c的ethif_probe函数中实现的,关键代码:
 if (probe_list(dev, eisa_probes) == 0)
  return 0;
eisa_probes在前面定义成全局:
static struct devprobe eisa_probes[] __initdata = {
#ifdef CONFIG_DE4X5             /* DEC DE425, DE434, DE435 adapters */
 {de4x5_probe, 0},
#endif
…………
 {NULL, 0},
};
我也照着添加了:
 if (probe_list(dev, arm_probes) == 0)
  return 0;
并定义:
static struct devprobe arm_probes[] __initdata = {
#ifdef CONFIG_ARM
 {ne_probe, 0},
#endif
 {NULL, 0},
};
这样,编译内核启动,果然,显示出了输出结果。
(待续)

uClinux下移植Ne2000兼容的网卡驱动程序(2)
继续分析修改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),经过了几次宏定义以后,很快找到如下宏代码:
 (*(volatile unsigned char *)(a))
和我想的一样,就是靠这个访问外部总线的。我的8019在S3C44B0的Bank 5上,工作在跳线模式,算了一下,起始基地址就是0x0a000600。

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

#define E8390_CMD EI_SHIFT(0x00)  /* The command register (for all pages) */
/* Page 0 register offsets. */
#define EN0_CLDALO EI_SHIFT(0x01) /* Low byte of current local dma addr  RD */
#define EN0_STARTPG EI_SHIFT(0x01)

……
而EI_SHIFT根据不同的配置有两种定义,如下:

#if defined(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)
#define EI_SHIFT(x) (ei_local->reg_offset[x])
#else
#define EI_SHIFT(x) (x)
#endif

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

#elif defined(CONFIG_ARM) || defined(CONFIG_ARM_MODULE)  //--by threewater
#define EI_SHIFT(x) ((x)*2)

对应的,在ne.c也有类似的定义问题:
#define NE_CMD   0x00
#define NE_DATAPORT 0x10 /* NatSemi-defined port window offset. */
#define NE_RESET 0x1f /* Issue a read to reset, a write to clear. */
#define NE_IO_EXTENT 0x20
添加成:
#ifdef CONFIG_ARM  //--by threewater
#define NE_CMD   0x00
#define NE_DATAPORT 0x20 /* NatSemi-defined port window offset. */
#define NE_RESET 0x3e /* Issue a read to reset, a write to clear. */
#define NE_IO_EXTENT 0x40
#else
……
这样,地址偏移的问题就基本解决了。当然,在Ne.c中,也有直接访问reg的代码,比如上面说的代码也相应的添加成:
#ifdef CONFIG_ARM  //--add by threewater
  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兼容的网卡找到了,接下来就不正常了。系统报告,反复陷入那个网卡的中断……
(待续)

uClinux下移植Ne2000兼容的网卡驱动程序(3)
反复陷入中断,很容易想到就是中断模式配置的问题,8019的中断是高电平有效,看看S3C44B0上的配置,果然不对。这个配置是在Bootloader中做好了的,改一下,就好了。我把他改成了上升沿触发。

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

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

按理说,ping的时候,第一次不知道目的主机的Mac地址,所以,应该发送ARP广播,发送的数据大概的格式开头应该是FF FF FF FF FF FF AA BB CC DD EE FF…………(AA BB CC DD EE FF表示发送方的Mac地址),可是我抓到的数据包是FF FF FF FF FF FF AA AA BB BB CC CC DD DD EE EE FF FF…… 看明白了,这个问题应该是网卡发送的时候,向网卡写入数据连续写了两次。这个问题最容易让人想到是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/armnommu/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 'Base Address for NE2000 ethernet' ARM_NE2000_BASE youraddr
   hex 'IRQ for NE2000 ethernet' ARM_NE2000_IRQ yourinterrupt

好了,编译通过。运行,果然没有问题了。Ping可以,telnet可以,在内核中把NFS打开,mount –t nfs ……也好用。哈哈。太好了。至此,8019在S344B0组成的uclinux平台上的驱动,移植成功。相信其他的网卡芯片一直驱动程序也基本是这个思路。现在写出来与大家共享。

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

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

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

 

2006-8-15 0:04:00
Re:uClinux下移植Ne2000兼容的网卡驱动程序——还是老文章
发现自己找的很多参考资料都是三水兄写的:)
2006-7-31 16:01:00
Re:uClinux下移植Ne2000兼容的网卡驱动程序——还是老文章
果然是MAC设置的不对,修改代码后,好用了。谢谢三水
By 柳暗花明(游客) | 个人主页 | 引用 | 返回 | 删除 | 回复
2006-7-29 20:27:00
Re:uClinux下移植Ne2000兼容的网卡驱动程序——还是老文章
三水兄:
有没有Philips的LPC2XXX系列较容易移植的bootloader , 我相对熟悉Uboot但是查了好像里边没有类似的板子用这类ARM,您的项目经验比较多,能否给个建议.多谢!

以下为blog主人的回复:

  如果是Linux下的bootloader,比较简单的就是blob了。针对arm体系,都比较容易。你可以看看。

By yixin8611(游客) | 个人主页 | 引用 | 返回 | 删除 | 回复
2006-7-26 11:57:00
调试uClinux2.6.9内核的RTL8019网络驱动。
在上次的问题中,我就是使用抓包工具分析ARP和ICMP的。我让开发板输出接收和发送的数据包的各个位到串口终端。
从开发板ping上位机时,上位机能接收到开发板的ARP请求(抓包软件反映的数据与开发板通过终端显示的一致),开发板却无法接收到上位机的ARP响应(抓包软件显示ARP响应已经发送,但是开发板没有显示接收到数据包)。
从上位机ping开发板时,开发板能接收到上位机的ARP请求(抓包软件抓取的数据包与开发板在终端显示的一致),并向上位机成功发送了ARP响应(开发板在终端显示的数据包与抓包软件显示上位机接收到的数据一致)。但随后开发板无法接收到上位机的ICMP请求(抓包软件显示上位机发送了ICMP请求,但开发板没有在终端显示接收到数据包)。但却能接收到上位机随后发出的UDP(抓包软件显示的内容与开板在终端显示的一致)。
我现在找不到问题的原因,一点头绪都没有。为何一开发板发送数据后就无法接收ARP和ICMP呢??找不到问题的原因。


以下为blog主人的回复:

 我说的noncache空间你设置对了?你可以测试,网卡的中断信号是不是产生了?网卡的mac地址那几个寄存器设置的是不是正确?

arp请求是广播包,如果MAC设置的不对,你就只能收到广播包了。

By 柳暗花明(游客) | 个人主页 | 引用 | 返回 | 删除 | 回复
2006-7-25 10:14:00
求助,S3C44B0X调试uClinux2.6.9内核的RTL8019网络驱动。
我在我的开发板移植了uClinux2.6.9forS3C44B0X,但是网络还有一点问题。我的开发板上的RTL8019工作在8位DMA模式下,我在驱动rtl8019.c中将16位的定义注释掉了。收发数据的类型已经设置为8位的,将DCR设置为8位DMA。并根据我的板子设置了基地址和偏移量、设置了处理器外部中断 和网卡中断模式、定义了ARMV4。 三水在文章中提到了“缓冲区范围的设置,请问在哪里进行缓冲区范围设置呢?”
我现在从开发板ping上位机时,上位机能接收到开发板的ARP请求,开发板却无法接收到上位机的ARP响应,
从上位机ping开发板时,开发板能接收到上位机的ARP请求,并向上位机成功发送了ARP响应。但随后开发板无法接收到上位机的ICMP请求。但去能接收到上位机的UDP。
现在我很疑惑的是为什么我的8019一发送数据后就无法接收ARP和ICMP,却能接收UDP呢??是协议的问题?缓冲区的设置问题?中断的问题?还是其他的问题?我现在找不到引起这个问题的原因,还请三水给与帮助。

以下为blog主人的回复:

我说的缓冲区范围,在44b0上,就是设置那两个noncache寄存器,把网卡空间的cache关掉。你可以配合抓包软件分析结果。看看各个数据包中,各个字节什么地方不对,还是数据包没有发出来。最好有Hub(不是交换机),来配合抓包软件,就可以得到所有的数据包了。

By 柳暗花明(游客) | 个人主页 | 引用 | 返回 | 删除 | 回复
2006-6-22 11:28:00
Re:uClinux下移植Ne2000兼容的网卡驱动程序——还是老文章
网卡工作模式的问题,16位还是8位。这往住引起误解。网卡的16位模式与通常所讲的CPU对外部总线的16位模式是有区别的。对8019as,其8/16位指的是DMA的数据宽度,并不是指I/O寄存器访问的宽度。即使是16位模式下,其I/O地址仍可以对A0地址线译码。所以如果在CPU中把网卡I/O设为16位,而网卡与CPU间的地址线是一对一连接,(CPUA0 接网卡A0 CPUA1接网卡A1…),就会使网卡的奇地址寄存器无法访问,这肯定会使网卡无法正常工作,因为CPU对16位外设访问A0始终为0。(以上是我查资料后的个人理解,请三水指正)博创的开发板上A1接SA0,而且RTL8019工作在16位的工作模式。在程序RTL8019.h中我已经将网卡基地址和偏移地址乘以2(左移一位)。我的问题是:
那这样不就是8位操作吗?虽然跟16位CPU接口,但是只是低8位是有效值,那怎么体现出RTL8019的16位操作呢?收发数据包是用16位,而我的原程序是8位工作模式下的,在传输时,是不是会出现错误的幀呢?我的上位机和开发板进行TFTP时,网卡的灯一直闪烁,而终端显示no found file。是不是因为使用8位模式的驱动程序,在收发包时出现的问题,将接收的幀认为是错误的? 对网卡的驱动这块儿,我有点晕了!请三水指点迷津!如何修改原程序呢?

以下为blog主人的回复:

 恩,有问题发邮件吧。别在这里贴太多代码,影响阅读的。

我Email:threewaterl@163.com

By pjfzyx(游客) | 个人主页 | 引用 | 返回 | 删除 | 回复
2006-6-22 9:53:00
Re:uClinux下移植Ne2000兼容的网卡驱动程序——还是老文章
我最近在做U-Boot的移植,我使用的是博创的UP-ARMNET300开发板,听说三水是博创的技术总监,所以想向三水请教一下。我的开发板使用的是S3C44B0的CPU,使用RTL8019网络芯片。不过博创的开发板上A1接SA0,而且RTL8019工作在16位的工作模式。在程序RTL8019.h中我已经将网卡基地址和偏移地址乘以2(左移一位)。但是网络工作不正常,后来UBoot中的RTL8019驱动程序是工作在8位模式下的。我是一个新手,对于这方面还不太了解,我该怎么修改才能让驱动支持16位模式呢?注:uBoot中RTL8019的驱动程序为RTL8019.h和RTL8019.C。我的邮箱是peijianfeng2002@yahoo.com.cn,希望您能给与解答,望您不吝赐教。
By pjfzyx(游客) | 个人主页 | 引用 | 返回 | 删除 | 回复
2006-6-21 17:33:00
Re:uClinux下移植Ne2000兼容的网卡驱动程序——还是老文章
恩,没错,是这个样子。
By threewater | 个人主页 | 引用 | 返回 | 删除 | 回复
2006-6-21 17:25:00
Re:uClinux下移植Ne2000兼容的网卡驱动程序——还是老文章
网卡的16位模式与我们通常所讲的CPU对外部总线的16位模式是有区别的。对8019as,其8/16位指的是DMA的数据宽度,并不是指I/O寄存器访问的宽度。
By pjfzyx(游客) | 个人主页 | 引用 | 返回 | 删除 | 回复

发表评论:

    昵称:
    密码: (游客无须输入密码)
    主页:
    标题:

日历

<<  < 2007 - 12 >  >>
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

公告

最新文章

文章分类

MY Favorite Blogs

最近评论

最近留言

信息

登陆