Internet作为全球最大的互联网络,几乎总汇了全球的信息资源。作为我们生活以及发展的基础设施,越来越多的设备需要连接Internet,共享信息资源。由于Internet的开放性,任何厂家基于TCP/IP协议、Internet技术生产的网络适配器均能接入Internet,与全球计算机互连通信。笔者此处就DM9000A网卡驱动实现作一个简单的介绍。
1. DM9000A概述
DM9000A为台湾DAVICOM公司推出的高速以太网接口芯片,该芯片完全集成的和符合成本效益单芯片快速以太网MAC控制器与一般处理接口,支持8位、16位、32位IO总线接口访问,完全符合IEEE 802.3u规范。
2. MAC帧
TCP/IP协议为Internet互联协议,采用4层层级结构:应用层、运输层、网络层、链路层。其中网卡工作在数据链路层,因此只能够发送或接收数据链路层的帧数据。大多数的TCP/IP均采用Ethernet V2帧格式,数据链路层的上一层的帧包,如网络层的数据包ARP包、RARP包、IP数据包(TCP包/UDP包)等,通过在包头加入6 字节MAC目的地址、6字节MAC源地址、2字节高层包类型,在包尾加入4字节帧检验序列即构成MAC帧。
MAC帧要求数据长度在64 byte ~ 1518 byte之间,这是由协议的防碰撞机制、信道利用率等决定的。因此链路层的上一层包帧长度要求在46 byte ~ 1500 byte,如果数据包长度小于46 byte,如ARP包、RARP包,则必须填充到46 byte,如果一次数据包大于1500byte,也必须进行分包传输。不然会被接收端网卡认为是错误的包,直接丢弃。
接收端网卡接收到CRC正确的MAC帧之后,根据MAC帧的MAC目的地址,比较自身MAC地址,如果一致,或者是组播地址、广播地址,则认为是传给自身的包,接收下来,并传给上一层进行处理,否则直接丢弃不属于自身的包。数据链路层并没有重传或者确定功能,对于传输过程出错的包,仅仅是丢弃,数据包的可靠传输、出错处理等需上面的层级处理。
3. DM9000A驱动
网卡驱动一般应实现最基本的三个接口,网卡初始化、网卡发送包、网卡接收包。
3.1. 网卡初始化
网卡初始化函数主要对DM9000A进行总线引脚的配置以及总线访问时序的设置,配置相应的中断处理,并初始化相应的DM9000A寄存器,最后使能相应的发送/接收回路。芯片总线访问时序应根据芯片手册设置最佳的参数,不然CPU直接访问总线将极大地降低CPU性能。中断处理为CPU提供了一种异步工作的方式,只有相应的事件到来,CPU才需处理相应的事件,能够加快系统响应以及提高系统效率,此处根据中断信号确定包发送完事件,包接收事件,网络连接/断开事件。最后,根据相应的手册或规范完成DM9000A寄存器的配置并使能收发回路。
int DM9000_Init(void)
{
uint32_t Id;
int32_t i;
int32_t oft;
// Memory Bank1, 16 bit
SROM_BW_REG &= ~(0xf << 4);
SROM_BW_REG |= (0<<7) | (0<<6) |(1<<5) | (1<<4);
// DM9000A最大的连续读/写时间为4clk(20ns/clk),一次读/写周期设置在100ns, 13 clk@133M
// Tacs 0, Tcos 1, Tacc 4, Tacp 6, Tcoh 1, Tcah 1
SROM_BC1_REG = (4<<4) | (1<<8) |(1<<12) | (6<<16) | (1<<24) | (0<<28);
MP01CON_REG = (MP01CON_REG & (~(0xf<<4))) |(2<<4); // SROM_CSn[1]
// set pin interrupt
GPH1CON_REG = GPH1CON_REG | (0xf<<4); // EINT_9
//GPH1PUD_REG = (GPH1PUD_REG & (~(0x3<<2)))| (0x1<<2); // pull down
EXT_INT_1_CON_REG = (EXT_INT_1_CON_REG &(~(0x7<<4))) | (3<<4); // rising edge
IRQ_RegisterInt(INT_EINT9, EINT9_Handler);
IRQ_EnableInt(INT_EINT9);
EXT_INT_1_PEND_REG |= (1<<1); // clear pending
EXT_INT_1_MASK_REG &= ~(1<<1); // enableEINT9
// RESET device
DM9000_WriteReg(DM9000_NCR, NCR_RST);
Delay_us(100);
DM9000_WriteReg(DM9000_NCR, NCR_RST);
Delay_us(100);
// Read id
Id = DM9000_ReadReg(DM9000_VIDL);
Id |= DM9000_ReadReg(DM9000_VIDH) << 8;
Id |= DM9000_ReadReg(DM9000_PIDL) << 16;
Id |= DM9000_ReadReg(DM9000_PIDH) << 24;
if (Id == DM9000_ID)
Debug("DM9000found at 0x%08x, id: 0x%08x\n", DM9000_IO, Id);
else {
Debug("DM9000not found at 0x%08x, id: 0x%08x\n", DM9000_IO, Id);
return -1;
}
DM9000_WriteReg(DM9000_GPR, 0x00); // Enable PHY
Delay_us(1000);
// Set PHY
DM9000_WritePhy(0x04, 0x101); // Set PHY media mode
DM9000_WritePhy(0x00, 0x3100);
// Program operating register
DM9000_WriteReg(DM9000_NCR, 0x0); // only intern phy supported by now
DM9000_WriteReg(DM9000_TCR, 0); // TX Polling clear
DM9000_WriteReg(DM9000_BPTR, 0x3f); // Less 3Kb, 600us
DM9000_WriteReg(DM9000_FCTR,FCTR_HWOT(3)|FCTR_LWOT(8));// Flow Control : High/Low Water
DM9000_WriteReg(DM9000_FCR, 0x08);
DM9000_WriteReg(DM9000_SMCR, 0); // Special Mode
DM9000_WriteReg(DM9000_NSR,NSR_WAKEST|NSR_TX2END|NSR_TX1END); // clear TX status
DM9000_WriteReg(DM9000_ISR, 0x0F); // Clear interrupt status
// Set Node address
oft = 0x10;
for (i=0; i<6; i++) {
DM9000_WriteReg(oft,default_enetaddr[i]);
oft++;
}
oft = 0x16;
for (i=0; i<6; i++) {
DM9000_WriteReg(oft,0);
oft++;
}
oft = 0x10;
Debug("MAC: ");
for (i=0; i<5; i++) {
Debug("%02x:",DM9000_ReadReg(oft));
oft++;
}
Debug("%02x\n", DM9000_ReadReg(oft));
// Activate DM9000
DM9000_WriteReg(DM9000_RCR, RCR_DIS_LONG|RCR_DIS_CRC|RCR_RXEN);//RX enable
DM9000_WriteReg(DM9000_IMR, 0xa3); // Enable TX/RX/Link interrupt
return 0;
}
3.2. 网卡发送包
写入网卡发送缓存的数据包应该是MAC帧,一次写入不能大于MAC帧最大长度,由于初始化时使能了帧填充以及帧检测序列填充功能,在启动发送时,发送缓存数据如果小于64 byte,会自动进行填充到最小MAC帧长度包,并自动加入CRC发送出去。DM9000A写发送缓存需要相应的寄存器访问序列,应保证是原子操作,即写缓存过程中不能产生其它的DM9000A事件中断(如接收中断),不然在中断中访问了其它的DM9000A寄存器后再返回继续写缓存将不会成功。因此在写缓存前先禁止DM9000A中断,写完后再开启中断。
int DM9000_SendPacket(void *pBuffer, int32_t Len)
{
uint16_t *pTemp;
int32_t TempLen;
int32_t i;
int8_t TempStatus;
if ((pBuffer == NULL) || (Len==0))
{
return -1;
}
// 禁止所有中断
DM9000_WriteReg(DM9000_IMR, 0x80);
// Move data to DM9000 TX RAM
__REGb(DM9000_IO) = DM9000_MWCMD;
pTemp = (uint16_t *)pBuffer;
TempLen = (Len+1) / 2;
for (i=0; i<TempLen; i++)
__REGw(DM9000_DATA)= pTemp[i];
// Set TX length to DM9000
DM9000_WriteReg(DM9000_TXPLL, Len&0xff);
DM9000_WriteReg(DM9000_TXPLH,(Len>>8)&0xff);
// Issue TX request command
DM9000_WriteReg(DM9000_TCR, TCR_TXREQ);
// 重新使能中断
DM9000_WriteReg(DM9000_IMR, 0xa3);
DM9000_TxStatus = 0;
while ((TempStatus = DM9000_TxStatus) != NET_TX_OK) {
if(TempStatus != 0)
{
returnTempStatus;
}
}
return 0;
}
3.3. 网卡接收包
在相应的中断处理中判断出相应的接收包事件后,即可启动读DM9000A接收缓存序列,将数据包从网卡读入到内存。同样,读数据包缓存也应保证原子操作。
int DM90000_ReceivePacket(void *pBuffer)
{
int Status;
int i;
int RxLen;
uint8_t RxReady;
if (!DM9000_RxStatus) {
return 0; // 没有包接收事件
}
// 禁止所有中断
DM9000_WriteReg(DM9000_IMR, 0x80);
DM9000_RxStatus = 0;
// 接收到包
RxReady = DM9000_ReadReg(DM9000_MRCMDX); // Dummy read
RxReady = DM9000_ReadReg(DM9000_MRCMDX); // Got mostupdated data
// 0:not packet, 1:packet received, other:error
if (RxReady != 0x01) {
if (RxReady) {
DM9000_WriteReg(DM9000_RCR,0x00); // Stop Device
DM9000_WriteReg(DM9000_IMR,0x80); // mask int
Debug("DM9000error: status check fail: 0x%x\n", RxReady);
return-1;
}
DM9000_WriteReg(DM9000_IMR,0xa3);
return 0;
}
// A packet ready now & Get status/length
__REGb(DM9000_IO) = DM9000_MRCMD;
__REGw(DM9000_DATA); // status
RxLen = __REGw(DM9000_DATA);
if ((RxLen>DM9000_PKT_MAX) || (RxLen<0x40)) {
Debug("DM9000error packet: RX Len 0x%x\n", RxLen);
Status = -1;
}
else {
Status =RxLen;
}
// Read received packet from RX SRAM
RxLen = (RxLen+1) / 2;
for (i=0; i<RxLen; i++) {
((uint16_t*)pBuffer)[i] = __REGw(DM9000_DATA);
}
DM9000_WriteReg(DM9000_IMR, 0xa3);
return Status;
}
3.4. 中断处理
DM9000A通过外部中断告知CPU相应的到来事件,应保证系统的实时,一般在中断中不应进行直接的事件处理,而是设置相应的事件标志,进而唤醒相应的事件进程进行处理。
static void EINT9_Handler(void)
{
uint8_t IntStatus;
// 禁止所有中断
DM9000_WriteReg(DM9000_IMR, 0x80);
IntStatus = DM9000_ReadReg(DM9000_ISR);
DM9000_WriteReg(DM9000_ISR, IntStatus);
if (IntStatus & (1<<0)) {
DM9000_RxStatus= NET_RX_OK;
}
if (IntStatus & (1<<1)) {
// 发送完成
DM9000_TxStatus= NET_TX_OK;
}
if (IntStatus & (1<<5)) {
// 连接改变
if(DM9000_ReadReg(DM9000_NSR) & (1<<6)) {
Debug("Link OK\n");
DM9000_LinkStatus = 1;
}
else {
Debug("Link failed\n");
DM9000_LinkStatus = 0;
}
}
EXT_INT_1_PEND_REG |= (1<<1); // clear pending
// 重新使能中断
DM9000_WriteReg(DM9000_IMR, 0xa3);
}
4. 驱动测试
此处应用以ARP包为例,ARP(地址解析协议)是根据IP地址来获取MAC地址的一种协议,上层应用是使用IP地址,但实现链路是使用MAC地址通信的。发送主机将包含目标IP地址的ARP请求广播到网络上的所有主机,目标IP地址的主机接收到该请求后,将返回ARP应答包给发送主机,应答包中即包含目标IP的MAC地址。
应用测试通过构造ARP包并发送出去,解析接收到的ARP应答包,并显示相应的目标MAC地址。
目标板可以通过路由器直接连接到局域网,也可以直连PC端网卡进行测试。连接PC端网卡时,应静态设置PC端IP地址以及目标板IP地址,应在同一局域网内。连接PC端网卡时并不用区分用交叉网线还是用直连网线,目前大部分的PC网卡都是自适应的,都是可以通信的。目标板连接路由器,应设置路由器端口所在的局域网IP。
void main(void)
{
uint16_t PacketBuffer[512];
ARP_PKT Arp_Packet;
ARP_PKT *pArp;
uint32_t IP_Addr;
int Status;
int Len;
uint8_t Command;
Uart_Init();
DM9000_Init();
memcpy(Arp_Packet.et_hdr.et_dst, NetBcastAddr, 6);
memcpy(Arp_Packet.et_hdr.et_src, default_enetaddr, 6);
Arp_Packet.et_hdr.et_type = htons(PROT_ARP);
// Ethernet hardware address
Arp_Packet.arp_hdr.ar_hrd = htons(0x0001);
Arp_Packet.arp_hdr.ar_pro = htons(PROT_IP);
Arp_Packet.arp_hdr.ar_hln = 6;
Arp_Packet.arp_hdr.ar_pln = 4;
Arp_Packet.arp_hdr.ar_op = htons (ARPOP_REQUEST);
// source ET addr
memcpy(Arp_Packet.arp_hdr.ar_sha, default_enetaddr,6);
// source ip addr
IP4_ADDR(IP_Addr, IP_ADDR[0], IP_ADDR[1], IP_ADDR[2],IP_ADDR[3]);
memcpy(Arp_Packet.arp_hdr.ar_spa, &IP_Addr, 4);
// dest ET addr = 0
memset(Arp_Packet.arp_hdr.ar_tha, 0, 6);
IP4_ADDR(IP_Addr, DST_ADDR[0], DST_ADDR[1],DST_ADDR[2], DST_ADDR[3]);
memcpy(Arp_Packet.arp_hdr.ar_tpa, &IP_Addr, 4);
printf("1. ARP Request test\n");
while (1) {
Len = DM90000_ReceivePacket(PacketBuffer);
if (Len > 0) {
printf("Rxlen %d\n", Len);
pArp =(ARP_PKT *)PacketBuffer;
if(ntohs(pArp->et_hdr.et_type) == PROT_ARP) {
if(ntohs(pArp->arp_hdr.ar_op) == ARPOP_REPLY) {
// ARP 应答包
printf("DstIP: %d.%d.%d.%d, Dst MAC: %2X:%2X:%2X:%2X:%2X:%2X\n",
DST_ADDR[0],DST_ADDR[1], DST_ADDR[2], DST_ADDR[3],
pArp->arp_hdr.ar_sha[0],pArp->arp_hdr.ar_sha[1], pArp->arp_hdr.ar_sha[2],
pArp->arp_hdr.ar_sha[3],pArp->arp_hdr.ar_sha[4], pArp->arp_hdr.ar_sha[5] );
printf("1. ARP Requesttest\n");
}
}
}
Command = Uart_TestChar();
if (!Command) {
Command =Uart_WaitChar();
Status =DM9000_GetLinkStatus();
if (Status) {
if (Command== '1') {
printf("SourceIP: %d.%d.%d.%d, Source MAC: %2X:%2X:%2X:%2X:%2X:%2X\n",
IP_ADDR[0],IP_ADDR[1], IP_ADDR[2], IP_ADDR[3],
default_enetaddr[0],default_enetaddr[1], default_enetaddr[2],
default_enetaddr[3],default_enetaddr[4], default_enetaddr[5] );
DM9000_SendPacket(&Arp_Packet,sizeof(ARP_PKT));
}
}
else {
printf("Waitingfor net connection\n");
}
}
}
}
5. 附录
OK210_IAR_DM9000.rar,包含DM9000A驱动模块实现及ARP测试应用工程。
源码:http://pan.baidu.com/s/1ntpH3zN