Porting 8019的驱动,是比较老的isa网卡,相对来说比ide驱动要复杂一些,和ide一样有自己的RAM和控制寄存器
1. RAM分布
RAM区为16K字节,地址为0x4000~0x7fff,RAM按页存储,每256字节为一页,一般将RAM的前12页(即0x4000~0x4bff)存储区作为发送缓冲区,后52页(即0x4c00~0x7fff)存储区作为接收缓冲区
控制寄存器区为32字节,地址为0x0000~0x001f,寄存器分为4页:PAGE0、PAGE1、PAGE2、PAGE3,由RTL8019AS的CR(Command Register命令寄存器)中的PS1、PS0位来决定要访问的页
2. 映射后的寄存器地址
经过cs片选之后,映射的8019的RAM和控制寄存器有了自己的物理地址,控制寄存器的物理地址需要根据硬件连接8BITS还是16BITS来计算
/*
* This code works in 8bit mode.
* If you need to work in 16bit mode, PLS change it!
*/
#include <asm/types.h>
#include <config.h>
#ifdef CONFIG_DRIVER_RTL8019_16BITS
#define ADDR_SFT 1
#else
#define ADDR_SFT 0
#define RTL8019_REG_00 (RTL8019_BASE + (0x00<<ADDR_SFT))
#define RTL8019_REG_01 (RTL8019_BASE + (0x01<<ADDR_SFT))
#define RTL8019_REG_02 (RTL8019_BASE + (0x02<<ADDR_SFT))
#define RTL8019_REG_03 (RTL8019_BASE + (0x03<<ADDR_SFT))
#define RTL8019_REG_04 (RTL8019_BASE + (0x04<<ADDR_SFT))
#define RTL8019_REG_05 (RTL8019_BASE + (0x05<<ADDR_SFT))
#define RTL8019_REG_06 (RTL8019_BASE + (0x06<<ADDR_SFT))
#define RTL8019_REG_07 (RTL8019_BASE + (0x07<<ADDR_SFT))
#define RTL8019_REG_08 (RTL8019_BASE + (0x08<<ADDR_SFT))
#define RTL8019_REG_09 (RTL8019_BASE + (0x09<<ADDR_SFT))
#define RTL8019_REG_0a (RTL8019_BASE + (0x0a<<ADDR_SFT))
#define RTL8019_REG_0b (RTL8019_BASE + (0x0b<<ADDR_SFT))
#define RTL8019_REG_0c (RTL8019_BASE + (0x0c<<ADDR_SFT))
#define RTL8019_REG_0d (RTL8019_BASE + (0x0d<<ADDR_SFT))
#define RTL8019_REG_0e (RTL8019_BASE + (0x0e<<ADDR_SFT))
#define RTL8019_REG_0f (RTL8019_BASE + (0x0f<<ADDR_SFT))
#define RTL8019_REG_10 (RTL8019_BASE + (0x10<<ADDR_SFT))
#define RTL8019_REG_1f (RTL8019_BASE + (0x1f<<ADDR_SFT))
驱动可以用轮询或者中断来实现,比如u-boot中就是用的轮询,不知道为什么它要用轮询?实际应用程序中清一色用的是中断,毕竟效率高
3. u-boot中的轮询驱动
轮询从网络收数据包的整个流程是:
事先CPU对8019占用的端口做初始化,记得CPU初始化的时候关8019的中断,因为是轮询的;另外通过8019的CS片选已经知道了RAM和控制寄存器的物理地址了
经过eth_init初始化,通过8019的控制寄存器物理地址,配置这些控制寄存器,包括中断触发方式,边沿触发或者电平触发;配置MAC地址到控制寄存器等
APP起3层的一些应用的时候,会调eth_rx函数,这个函数是一个循环,读取中断状态寄存器值,轮询是否有数据到网卡
之后调用NetReceive从网卡RAM中读取数据,RTL8019_BOUNDARY是接收缓冲环读页指针,初始化=PSTART,RTL8019_CURRENT是接收缓冲环写页指针,初始化=PSTART,它们组成一个接收缓冲环;也就是说RTL8019_CURRENT是8019硬件根据数据包来的时候会自动修改这个寄存器的,驱动中读取RAM中数据后,驱动会即时修改RTL8019_BOUNDARY的;具体参考注释
轮询发送相对来说简单,很好理解
/* Get a data block via Ethernet */
extern int eth_rx (void)
{
unsigned char temp, current_point;
put_reg (RTL8019_COMMAND, RTL8019_PAGE0);
while (1) {
temp = get_reg (RTL8019_INTERRUPTSTATUS);
if (temp & 0x90) {
/*overflow */
put_reg (RTL8019_COMMAND, RTL8019_PAGE0STOP);
udelay (2000);
put_reg (RTL8019_REMOTEBYTECOUNT0, 0);
put_reg (RTL8019_REMOTEBYTECOUNT1, 0);
put_reg (RTL8019_TRANSMITCONFIGURATION, 2);
do {
current_point = nic_to_pc ();
} while (get_reg (RTL8019_BOUNDARY) != current_point);
put_reg (RTL8019_TRANSMITCONFIGURATION, 0xe0);
}
if (temp & 0x1) {
/*packet received */
do {
put_reg (RTL8019_INTERRUPTSTATUS, 0x01);
current_point = nic_to_pc ();
} while (get_reg (RTL8019_BOUNDARY) != current_point);
}
if (!(temp & 0x1))
return 0;
/* done and exit. */
}
}
static unsigned char nic_to_pc (void)
{
unsigned char rec_head_status;
unsigned char next_packet_pointer;
unsigned char packet_length0;
unsigned char packet_length1;
unsigned short rxlen = 0;
unsigned int i = 4;
unsigned char current_point;
unsigned char *addr;
/*
* The RTL8019's first 4B is packet status,page of next packet
* and packet length(2B).So we receive the fist 4B.
*/
put_reg (RTL8019_REMOTESTARTADDRESS1, get_reg (RTL8019_BOUNDARY));
put_reg (RTL8019_REMOTESTARTADDRESS0, 0x00);
/* 读取RTL8019_BOUNDARY接收缓冲环读指针,并写入起址寄存器 */
put_reg (RTL8019_REMOTEBYTECOUNT1, 0x00);
put_reg (RTL8019_REMOTEBYTECOUNT0, 0x04);
/* 因为4B为包状态,所以先读4B,将0x04写入到长度寄存器 */
put_reg (RTL8019_COMMAND, RTL8019_REMOTEDMARD);
/* 执行DMA操作 */
rec_head_status = get_reg (RTL8019_DMA_DATA);
next_packet_pointer = get_reg (RTL8019_DMA_DATA);
packet_length0 = get_reg (RTL8019_DMA_DATA);
packet_length1 = get_reg (RTL8019_DMA_DATA);
/* 读取4B的状态寄存器,包括数据长度,下一个包的起址 */
put_reg (RTL8019_COMMAND, RTL8019_PAGE0);
/*Packet length is in two 8bit registers */
rxlen = packet_length1;
rxlen = (((rxlen << 8) & 0xff00) + packet_length0);
rxlen -= 4;
if (rxlen > PKTSIZE_ALIGN + PKTALIGN)
printf ("packet too big!/n");
/*Receive the packet */
put_reg (RTL8019_REMOTESTARTADDRESS0, 0x04);
put_reg (RTL8019_REMOTESTARTADDRESS1, get_reg (RTL8019_BOUNDARY));
put_reg (RTL8019_REMOTEBYTECOUNT0, (rxlen & 0xff));
put_reg (RTL8019_REMOTEBYTECOUNT1, ((rxlen >> 8) & 0xff));
put_reg (RTL8019_COMMAND, RTL8019_REMOTEDMARD);
/* 同前的操作,前是读4B的状态,这里是读实际的包 */
for (addr = (unsigned char *) NetRxPackets[0], i = rxlen; i > 0; i--)
*addr++ = get_reg (RTL8019_DMA_DATA);
/* 读取数据到*NetRxPackets指针 */
/* Pass the packet up to the protocol layers. */
NetReceive (NetRxPackets[0], rxlen);
while (!(get_reg (RTL8019_INTERRUPTSTATUS)) & 0x40); /* wait for the op. */
/*
* To test whether the packets are all received,get the
* location of current point
*/
put_reg (RTL8019_COMMAND, RTL8019_PAGE1);
current_point = get_reg (RTL8019_CURRENT);
/* 读取接收缓冲环写指针,返回给上层API判断用 */
put_reg (RTL8019_COMMAND, RTL8019_PAGE0);
put_reg (RTL8019_BOUNDARY, next_packet_pointer);
/* 将下一个包的指针赋值给读取接收缓冲环读指针 */
return current_point;
}
这是采用的Remote DMA READ方式
初始化bnry读指针 = curr写指针 = PSTART
有数据包要读时,查看bnry是否=curr,不等则说明有数据包要读
用bnry初始化DMA起址寄存器0,1
用4初始化DMA长度寄存器0,1
执行Remote DMA READ命令
读出网卡物理状态头(4字节),从包头中读出包长度
同2-5读出所有数据
调整bnry指针=curr
4. 中断驱动
中断实现方式相比轮询效率高,代码框架也类似,采用的是SEND COMMAND方式
5. 小结
初始化流程严格按照流程做,做一个128*64的液晶显示驱动,自以为是的少初始化一个细节,结果不管怎么样都驱动不成功
自己的网卡和CPU是16位接口的,地址线有错位,CPU的A1连接到网卡芯片的A0,所以编程的时候需要移位调整