(5)RTL8019AS,RTL8029AS如何读写网卡的RAM

日期 2001-11-16
作者 老古 http://www.laogu.com Email:zhangshenggu@vip.163.com
内容

   要接收和发送数据包都必须读写网卡的内部的16k的ram,必须通过DMA进行读和写.网卡的内部ram是一块双端口的16k字节的ram.所谓双端口就是说有两套总线连结到该ram,一套总线A是网卡控制器读/写网卡上的ram,另一套总线B是单片机读/写网卡上的ram.总线A又叫Local DMA,总线B又叫Remote DMA.

  上图中虚线框住的部分为Remote DMA,也就是单片机对网卡ram进行读写的总线,对8019来说就是ISA总线.没有框住的部分(左边的部分),就是Local DMA,网卡控制器对网卡ram进行读写的总线.
其中的地址总线没有画出来,只画了数据总线.实际在ram的内部还有一些总线仲裁的逻辑,这里也没有画出来.所谓总线仲裁的逻辑就是为了实现两套总线都能进行对ram的读写,而不互相冲突.
网卡控制器读写网卡ram(Local DMA)的优先级比单片机读写网卡ram的优先级要高.优先级要高的意思是:

1.当两者都要请求控制总线时.Local DMA优先获得控制权.
2.高优先级的Local DMA可以中断Remote DMA,而Remote DMA不能中断Local DMA
3.在Remote DMA,也就是单片机对网卡ram读写的过程进行中可以被Local DMA中断,Local DMA中断Remote DMA,然后进行Local DMA的数据传输,Local DMA传输完毕之后继续刚才被中断的Remote DMA,以完成Remote DMA的传输.
  上图中的Remote就是Remote DMA的传输,Local burst就是Local DMA的传输.图的左边是一个Remote DMA被Local DMA中断的示意图.Remote DMA是等到Local Burst完成之后才结束该次的传输.被打断多久的时间取决于FT1,FT0(是DCR配置寄存器的位)


  单片机的总线要比网卡的DMA总线慢很多.网卡的DMA总线大概在10Mhz,而单片机的总线大概1Mhz.所以在Remote DMA的过程中不需要特别的等待时序.
  但是如果使用较快的CPU,比如DSP,ARM等,可能要考虑时序问题.也就是说IOCHRDY(ISA总线的一个信号,RTL8019AS),或者TRDY(PCI总线的信号 RTL8029AS) ,是需要考虑连到CPU上,或者做一定的处理.
  那么对于不快也不慢的AVR单片机来说,要不要接IOCHRDY?估计是要的.因为我不提供avr的上网方案,所以也没有做太多的研究.对于77E58来说可以不接IOCHRDY,因为77E58可以内部设置外部ram的存取的速度.
  DMA有8位和16位两种.网卡支持这两种DMA,一般我们使用8位的DMA,8位的DMA的接线比较少,同时适合单片机处理.电脑里一般使用16位DMA.

  有人问到在电脑里如何使用8位的DMA的问题.有些卡自动检测总线上的IOCS16B来选择总线,比如RTL8019as,我试过RTL8019as使用8位DMA在电脑里是失败的.如果真的要在电脑里使用8位的DMA,要把该引脚IOCS16B断开(可以割断),而不连到ISA总线上,这样这些网卡会自动的进行8位的操作(地址译码为10位).
对于使用DM9008芯片的网卡,16位DMA传输是由SLOT引脚决定的。我试过把DM9008的IOCS16B
引脚与ISA槽断开(通过贴“透明胶”的方法),配套的设置程序检查时死机。如果想DM9008
使用8位DMA操作,应该把SLOT引脚割断,而不是IOCS16B。

在DSP里可以使用16位的DMA.
  因为不同的单片机(CPU),代码可能不同,我在下面将用几种表示法来论述:
  假设用到的I/O地址为0xC000,读出到temp变量或写temp到寄存器,temp为8位变量:

通用的 RTL8019 C程序(mcs51) RTL8019 汇编的程序(mcs51)
temp=read_register(address)
读寄存器函数
temp=reg00; MOV DPTR,#address
MOVX A,@DPTR
MOV temp,A
write_register(address,temp)
写寄存器函数
reg00=temp; MOV temp,A
MOV DPTR,#address
MOVX @DPTR,A
  注:
#define reg00 XBYTE[0xc000]
注:address equ 0C000H


通用的表达式:
void write_register(unsigned char address,unsigned char value)或
void write_register(unsigned int address,unsigned char value)
unsigned char read_register(unsigned char address)或
unsigned char read_register(unsigned int address)
上面的表达式中,根据你的地址或寻址方法而选择unsigned int address或unsigned char address.
上表给出了用c语言或汇编语言或其他语言的表达的等价的程序.

下面给出用51单片机的c语言程序:
#define reg00 XBYTE[0xc000] //reg00- 10为isa网卡接口的寄存器地址240-250;
#define reg01 XBYTE[0xc100]
#define reg02 XBYTE[0xc200]
#define reg03 XBYTE[0xc300]
#define reg04 XBYTE[0xc400]
#define reg05 XBYTE[0xc500]
#define reg06 XBYTE[0xc600]
#define reg07 XBYTE[0xc700]
#define reg08 XBYTE[0xc800]
#define reg09 XBYTE[0xc900]
#define reg0a XBYTE[0xca00]
#define reg0b XBYTE[0xcb00]
#define reg0c XBYTE[0xcc00]
#define reg0d XBYTE[0xcd00]
#define reg0e XBYTE[0xce00]
#define reg0f XBYTE[0xcf00]
#define reg10 XBYTE[0xd000]
xdata unsigned char buffer[1536];//缓冲区,放在外部ram.
unsigned int count;//需要读或写的字节数
unsigned int i;

//DCR=0xc8;要配置DCR为8位的dma
void write_dma(unsigned int address,unsigned int count)//写网卡的ram

{//address为要写入到网卡里的ram的起始地址,count为要连续写入的字节数

page(0);
reg09=address>>8;//address high
reg08=address&0xff;//address low
reg0b=count>>8; //write count high
reg0a=count&0xff;//write count low
reg00=0x12 ;//dma write

for(i=0;i<count;i++)

{
reg10=buffer[i];

}
reg0b=0; // count high 中止DMA操作
reg0a=0;// count low
reg00=0x22;//abort/complete dma page 0

}
执行的结果是将buffer数组的内容被写入到网卡的起始ram地址address的一段ram里.
程序当中的最后3句:
reg0b=0; // count high
reg0a=0;// count low
reg00=0x22;//abort/complete dma page 0

可以不要,但推荐使用,以便发生错误的时候能够正确的退出DMA传输,3句是中止DMA的代码.在电脑里死机是很"正常"的,而在单片机里"死机"是大事,所以要考虑更多,一旦时序配合有问题,DMA就可能发生错误,提供发生错误时的恢复是有必要的.

void read_dma(unsigned int address,unsigned int count)//读网卡ram
{//address为网卡里的ram的起始地址,count为要连续读取的字节数
page(0);
reg09=address>>8;//address high
reg08=address&0xff;//address low
reg0b=count>>8; //write count high
reg0a=count&0xff;//write count low
reg00=0x0a ;//dma read

for(i=0;i<count;i++)

{
buffer[i]=reg10;

}

reg0b=0; // count high 中止DMA操作
reg0a=0;// count low
reg00=0x22;//abort/complete dma page 0


}
执行的结果是将网卡里的起始地址为address的共count个字节读入到buffer[i]里.

如果是使用模拟i/o,程序如下:
unsigned char read_register(unsigned char address)
{//读寄存器
unsigned char temp;
ea=0;//关闭中断是推荐的
p2=address;
p0=0xff; //这句不能省略
read=0;
temp=p0;
read=1;//read为单片机的读引脚
ea=1;
return(temp);

}

void write_register(unsigned char address,unsigned char value)
{
//写寄存器
ea=0;
p2=address;
p0=value;
write=0;//write为单片机的写引脚
write=1;
p0=0xff; //这句也是需要的.
ea=1;

}
那么我给出的read_dma,write_dma里的函数的i/o替换成模拟i/o的函数就可以了,举例如下:

#define reg10 0xd0
reg10=buffer[i] ;替换为write_register(reg10,buffer[i]);
buffer[i]=reg10 ;替换为buffer[i]=read_register(reg10);
其他reg0a,reg0b等也是做类似的替换.
  建议不要使用模拟i/o,因为速度慢,同时也不可靠.模拟i/o中一般要关闭中断. 因为如果不关闭中断,会引起被中断,使读或写寄存器的时间变得很长.尽管DMA有优先级,但是如果read或write线一直处于低电平的时候,能否被Local DMA中断,我是持有疑问的. 网卡有8字节的FIFO,假设在单片机读写网卡ram的同时,网卡收到一个数据包,那么FIFO最多可以存储64个bit就必须启动Local DMA写网卡ram,网卡的速率为10,000,000bit/秒,64个bit的时间为6.4微秒. 单片机读写一个字节的网卡ram的时间最好不要超过6.4微秒.所以关闭中断以尽快执行i/o操作.虽然资料上没有说是否可以超过6.4微秒.有兴趣的可以做实验看是否一个很慢的read,write会不会影响网卡收数据包.
  有时候我们不要相信资料.比如有些单片机书对MOVX指令的时序图就有误.RTL8019AS关于溢出时网卡不会覆盖以前接收的数据包的论述也是有误的. "尽信书不如无书"."实践是检验真理的唯一标准".就像资料说DM9008说支持8位DMA一样,我以前在电脑里试过,怎么也不行,用单片机的时候就可以.网卡并不是为单片机设计的,有些在电脑里不会出现的问题在单片机里可能碰到.
如果采用16位的dma(有些客户用dsp,或者在电脑里驱动,16位的dma要求有16条数据线的CPU,不能用在51单片机),那么也给出大家参考:
//DCR=0xC9;//配置成16位dma.
unsigned int buffer[768];//注意用int的数组

void write_dma(unsigned int address,unsigned int count)//写网卡的ram

{//address为要写入到网卡里的ram的起始地址,count为要连续写入的字节数

page(0);
reg09=address>>8;//address high
reg08=address&0xff;//address low
reg0b=count>>8; //write count high
reg0a=count&0xff;//write count low
reg00=0x12 ;//dma write
count=(count+1) /2;

for(i=0;i<count;i++)

{
reg10=buffer[i];

}
reg0b=0; // count high 中止DMA操作
reg0a=0;// count low
reg00=0x22;//abort/complete dma page 0

}

 

void read_dma(unsigned int address,unsigned int count)//读网卡ram
{//address为网卡里的ram的起始地址,count为要连续读取的字节数
page(0);
reg09=address>>8;//address high
reg08=address&0xff;//address low
reg0b=count>>8; //write count high
reg0a=count&0xff;//write count low
reg00=0x0a ;//dma read
count=(count+1) /2;

for(i=0;i<count;i++)

{
buffer[i]=reg10;

}

reg0b=0; // count high 中止DMA操作
reg0a=0;// count low
reg00=0x22;//abort/complete dma page 0


}

上面的count=(ount+1) /2,为什么要加一,是因为读取字节数为单数的时候就要.


来源
老古网

欢迎技术探讨和发表评论