Linux下8019网卡驱动程序福建鑫诺通信有限公司陈光平(chenggp_fj@)本文以S3C44B0的CPU为例,详细解析了linux下RTL8019网卡驱动程序工作原理,其间知识大多来源互联网络,特别是浙大潘纲的论文,在此不一一列出,此文目的只是让嵌入式linux爱好者得到更多网卡驱动的资料,并获得交流机会,不足之处请指正(一)、硬件相关部份1、CPU与网卡的连接方式(s3c44B0 CPU) (RTL 8019网卡)CPU与网卡接线图上图为S3c44b0CPU和网卡的接线图,此接法并非固定,如接法不同,则牵涉到很多相应的改动,下面会详细分析不同之处从硬件部门得到:网卡在CPU的存储空间上接BANK4,即0x08000000(看44B0手册)外部中断号为:EXTINT3 (irqs.h文件获得值为22)上面两个值可以查CPU手册,或询问硬件设计人员由上图可以知道以下数据:(1)、网卡与CPU地址线连接错开8位(A0接A8)(2)、总共连线,其实4根就足够用了,因为每根线可以译码4个地址空间,总共是16个地址空间,每个地址空间对应一个寄存器地址,而8019总共就是16个寄存器(3)、一般是跳线模式,不使用9346芯片1-1 基地址算法首先8019的基地址是300H(见RTL8019芯片资料:选择IO总线地址),但是有些硬件已在芯片中做过了偏移,比如我们的网卡已做了处理,基址已偏移到0x08000000,那么因为网卡A0接CPU的A8,表示基地址左移8位,下一个寄存器reg0的地址就是:0x08000100(0000,0000,0001 0001,0000,0000)还不理解的话我们看另一种接法:(S3C44B0 CPU ) (RTL8019芯片) 这种接法地址线只错开一位,我们来看(假如仍使用BANK4):A1 A0,为何A0不能接A0,因为8019是共用数据线和地址线的,如果A0接A0的话,无法配置8位和16位方式现在可以看到,基地址为300H 的话,左移一位(A0->A1),就是600H,于是算出基地址为: 0x08000600,下一个寄存器的地址就是0x08000602,因为都左移一位,相当于x2了2、8019网卡的工作原理本节主要讨论一下8019是怎么工作的,如下图:(本地DMA ) (远端DMA )(图2 与DMA 有关的寄存器 )2-1 远端DMA 和本地DMA首先解释一下远端DMA 和本地DMA 的区别,以发送为例,网络发送数据是下面这样一个流程:CPU 将数据先发送到网卡上的16KRAM 中,这之间必须要有一个数据通道,我们称为远端DMA ,而数据从网卡的RAM 中发出给链路,RTL8019控制器与RAM 之间的通道称为本地DMA2-2 CPU 读写数据到网络芯片先解释一下上面提到的几个重要的寄存器,我们从上面分阶段说明:RBCR0,RBCR1:存放要读写数据的长度RSAR0,RSAR1:存放数据到网络芯片(RAM)中存放的起始地址(而不是页号,但通常还是以某页的00为开始,如0x4000)CR:向命令寄存器发出Remote DMA开始指令上面的流程后面会详细解说2-3 网络芯片发数据到以太网CPU把数据用Remote DMA发到网络芯片之后,就可以用让网络芯片用Local DMA向外数据了,需要设置如下的控制器:TPSR:网络芯片要发送的数据在网络芯片RAM中的起始页号,所以发送的数据只能从某页的开头存放。
TBCR0,TBCR1:要发送的数据总长度CR:向命令寄存器发出发送数据包的指令CMD_XMIT然后程序就可以返回了,网络芯片会自动用Local DMA发数据包2-4 网络芯片从以太网读数据网络芯片在从以太网读数据过程中,会用到的寄存器如下:PSTART,PSTOP:网络芯片接收数据缓冲区的起始和终止页号。
形成一个接收缓冲环,每页256字节CURR:接收缓冲环写页指针,初始化=PSTARTBNRY:接收缓冲环读页指针,初始化=PSTART这四个寄存器在init函数里初始化,以后有数据包来到时,网络芯片会自动判断是否发给本机,是则用Local DMA存入数据,并自动修改读写指针RTL8019接收包帧结构:具体流程如下:有了上面的收发包的格式,如何发送和接收数据包呢?看下图:图3 发送缓冲区先将待发送的数据包存入芯片RAM,给出发送缓冲区首地址和数据包长度(写入TPSR,TBCR0,1),启动发送命令(CR=3E),即可实现8019发送功能.8019会自动按以太网协议完成发送并将结果写入状态寄存器。
那么8019是怎样接收数据的呢?请看下图:接收缓冲区构成一个循环FIFO队列,PSTART、PSTOP两个寄存器限定了循环队列的开始和结束页,CURR为写入指针,受芯片控制,BNRY为读出指针,由主机程序控制,根据CURR=BNRY+1?可以判断是否收到新的包,新收到的数据包按下图的格式存于以CURR 指出的地址为首址的RAM中当CURR=BNRY时芯片停止接收数据包总的说来,数据包是这样被主机写入芯片和从芯片RAM读出来的:主机设置好远端DMA 开始地址(RSAR0,1)和远端DMA数据字节数(RBCR0,1),并在CR中设置读写,就可以从远端DMA口寄存器读出芯片RAM里的数据或把数据写入RAM芯片,用个示例来说明一下(以发送为例):首先了解RBCR0,1中的远端DMA字节数在寄存器高低位上是怎么存储的?比如长度为600字节,如下:(十进制600=十六进制258)PG0W_RBCR0 = 600 & 0xff; //0x58PG0W_RBCR1 = 600 >> 8; //0x2同样道理写入地址:PG0W_RSAR0 = XMIT_START & 0xff;PG0W_RSAR1 = XMIT_START >> 8;然后发送:NIC_CR = (CR_PAGE0 | CR_DMA_WRITE | CR_START);此时CPU通过远端DMA发送了网卡RAM中的指定区域数据2-5网卡RAM的空间分配网卡RAM的空间分配(包括收缓冲区、发缓冲区、数据存储区),RAM空间原则上是随意分配的,看了资料很多的分配方案都是如下:RAM总共16k,可以用来存储数据的地址是0x4000-0x7FFF,为什么呢?看一下RAM的结构图7 网卡RAM结构图因为256个字节称为一页,所以0x4000-0x7FFF可以用页表示为0x40---0x7F,下面采用的也基本都是用页的地址,如:0x4000-0x40ff是一页,我们称该页为第0x40页(或页0x40),页码也就是16位地址的高8位. 0x4100-0x41ff 是一页,称为第0x41页.0x4000-0x7fff共16k字节的ram是网卡接收和发送数据包用的,该16k字节的ram实际上是双端口的ram,可以同时被网卡读/写和用户读/写,相互之间不影响,网卡读/写比用户读写的优先级高,举个例子:假如网卡收到一个数据包,需要放到0x5000开始的缓冲区,那么需要写入第0x50页.这时用户同时读取旧的数据包,该数据包放在0x4000开始的一段缓冲区,那么需要读第0x40页.网卡优先,它先写入一个字节,然后才读取一个字节到ISA总线上.内部总线的速度很快,是20Mhz,而ISA总线只有8Mhz, 网卡足够快进行同时读写数据,互不影响.既然如此,我们做如下定义:0x40---0x4B共12页做为网卡的发送缓冲区,刚好可以存储两个最大的网络包(约1.5k一个最大网络包),另外使用0x4C---0x7F为网卡的接收缓冲区,共52页,如下定义:Reg04=0x40;//参看RTL8019手册,04在寄存器页0发状态为TPSR,即发送首地址Reg01=0x4C;//参看RTL8019手册,01在寄存器页1写状态为PSTART,即接收首地址Reg02=0x80;//参看RTL8019手册,01在寄存器页2写状态为PSTOP,即接收尾地址2-6关于网卡MAC地址:0x00e0-0x00ff:52525454ABAB3D3D8E8E2C2C545446464C4C49494E4E404022222020575757 57 ,那么该网卡的地址是:5254AB3D8E2C,单地址和双地址的内容是重复的.一般使用偶数地址的内容,Prom是网卡在复位的时候从93C46里读出来的.如果你没有使用93C46,那么就不要使用Prom.,那么如何获得网卡的地址,有两种方法,一是直接读93C46,二是读Prom.网卡在工作的时候的网卡地址是由寄存器MAR0,MAR1,MAR2,MAR3,MAR4,MAR5决定,而不是93C46,也不是Prom. 而这几个寄存器的内容需要用户自己编写程序写入,一般可以读出Prom里的网卡地址,然后写入到这6个寄存器里.如果你没有使用93C46,那么Prom也是不可以使用的,这时要由你的程序自己指定一个网卡地址, 网卡地址不能跟别的网卡地址相同,写入到MAR0-MAR5里.例如使:MAR0=00MAR1=00MAR2=00MAR3=00MAR4=00MAR5=08那么网卡的地址就是:00:00:00:00:00:08所有发给00:00:00:00:00:08地址的数据包就可以被网卡收到.发送数据包的时候你的程序也要使用MAR0-MAR5的值作为发送的源地址.2-7 关于IO端口几乎每一种外设都是通过读写设备上的寄存器来进行的。
外设寄存器也称为“I/O端口”,通常包括:控制寄存器、状态寄存器和数据寄存器三大类,而且一个外设的寄存器通常被连续地编址。
CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/O-mapped),另一种是内存映射方式(Memory-mapped)。
而具体采用哪一种则取决于CPU的体系结构。
有些体系结构的CPU(如,PowerPC、m68k等)通常只实现一个物理地址空间(RAM)。
在这种情况下,外设I/O端口的物理地址就被映射到CPU的单一物理地址空间中,而成为内存的一部分。
此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。
这就是所谓的“内存映射方式”(Memory-mapped)。
而另外一些体系结构的CPU(典型地如X86)则为外设专门实现了一个单独地地址空间,称为“I/O地址空间”或者“I/O端口空间”。
这是一个与CPU的RAM物理地址空间不同的地址空间,所有外设的I/O端口均在这一空间中进行编址。
CPU通过设立专门的I/O指令(如X86的IN和OUT指令,linux的outb,inb等,后面代码有用到)来访问这一空间中的地址单元(也即I/O端口)。