5.3.2 基于Microblaze完成以太网接口的开发
推荐给好友
打印
加入收藏
更新于2008-11-18 20:16:15

完成DM9000接口驱动的开发,接下来就是实现以太网包成帧、解帧处理、收发包以及ARP等功能。本节主要给出相应的实现代码。

1.几个常用的数据结构体
在完成以太网接口开发之前,首先需要定义常用的结构体,这不仅使得程序条理清楚,还便于修改、扩充以及移植。本例所用的结构体包括:送往DM9000的以太网包头结构体ipethernet、IP地址结构体IP_NUMBER、数据包结构体eip以及ARP包结构体arp。

(1)以太网包头结构体ipethernet
由于DM9000具备MAC层处理功能,在软件端不需要加入7比特前导码,因此以太网包头结构体主要包括目的网卡地址,源网卡地址以及下一层协议的定义,其相应的代码如下:

typedef struct {
Xuint8 DestMacId[6]; /*目的网卡地址*/
Xuint8 SourceMacId[6]; /*源网卡地址*/
Xuint16 NextProtocal; /*上一层协议*/
} ipethernet;

(2)IP地址结构体IP_NUMBER
IP地址可用4个字节来表示,因此IP地址结构体IP_NUMBER可用一个深度为4的Xunit8数组来实现,其相应的代码如下:
typedef struct {
Xuint8 IP[4];
} IP_NUMBER;

(3)数据包结构体eip
数据包结构体的处理是由软件完成的,不受硬件限制。其中头4个比特为版本字段,包含了创建高数据包IP协议的版本信息,用来证实发送方、接收方和之间所有路由器所必需使用的格式,例如IPv4版本的版本号为4。接下来4个比特为首部长度。

typedef struct {
Xuint8 VerandIphLen; /*版本与头长度*/
Xuint8 ServerType; /*服务类型*/
Xuint16 TotalLen; /*总长度*/
Xuint16 FrameIndex; /*IP帧序号*/
Xuint16 Segment; /*分段标志*/
Xuint8 ttl; /*生存时间*/
Xuint8 NextProtocal; /*下一层协议*/
Xuint16 Crc; /*校验和*/
Xuint8 SourceIp[4]; /*源IP*/
Xuint8 DestId[4]; /*目的IP*/
}eip;

(4)ARP包结构体arp
ARP包的定义,其帧结构的说明如本章5.1.4节图5-5所示,除去前导码和以太网包头,只剩下两字节硬件类型、两字节的协议类型、1字节的MAC地址长度、1字节的IP地址长度、2字节的操作字段、6字节的发送端以太网地址、4字节的发送端IP地址、6字节的接收端以太网地址以及4字节的接收端IP地址,本例不使用CRC校验,相应的代码如下:

typedef struct { /* arp报文的内容总长28字节*/
Xuint16 HardwareType; /*以太网为0x0001*/
Xuint16 ProtocalType; /*ip 为0X0800*/
Xuint8 HardwareLen; /*=0X06*/
Xuint8 ProtocalLen; /*=0X04*/
/*操作 0X0001为请求 0X0002为应答 */
Xuint16 Operation;
/*0X0003为反向地址请求 0X0004为反向地址应答*/
Xuint8 SourceMacId[6]; /*源网卡地址*/
Xuint8 SourceIp[4]; /*源IP地址*/
Xuint8 DestMacId[6]; /*目的网卡地址*/
Xuint8 DestId[4]; /*目的IP地址*/
} arp ;

2.以太网发包函数
以太网发包函数Send_ethernet_Frame()为以太网数据包或ARP包添加包头并将其发送,其入口参数_pkst *TxdData为发送结构体指针,PROTOCOL下一层协议,分为IP协议(IP_PACKED)或ARP协议(ARP_PACKED);de_mac为目标MAC地址指针。返回值为1,表明发送成功。相应的代码如下:
Xuint8 Send_ethernet_Frame (
struct _pkst *TxdData, //结构指针
Xuint8 * de_mac, //对方的MAC地址
Xuint8 PROTOCOL //IP协议或ARP协议
)
{
ipethernet ethernet_head; //ETHERNET处理缓存区
Xuint8 ethernet_frame[MAX_PACKET_SIZE];
struct _pkst PKethernet;
Xuint16 len;
//设置对方MAC
ethernet_head.DestMacId[0]=*de_mac;
de_mac++;
ethernet_head.DestMacId[1]=*de_mac;
de_mac++;
ethernet_head.DestMacId[2]=*de_mac;
de_mac++;
ethernet_head.DestMacId[3]=*de_mac;
de_mac++;
ethernet_head.DestMacId[4]=*de_mac;
de_mac++;
ethernet_head.DestMacId[5]=*de_mac;
//设置本机MAC地址
ethernet_head.SourceMacId[0]=NetPort[num].My_Mac[0];
ethernet_head.SourceMacId[1]=NetPort[num].My_Mac[1];
ethernet_head.SourceMacId[2]=NetPort[num].My_Mac[2];
ethernet_head.SourceMacId[3]=NetPort[num].My_Mac[3];
ethernet_head.SourceMacId[4]=NetPort[num].My_Mac[4];
ethernet_head.SourceMacId[5]=NetPort[num].My_Mac[5];
#ifdef Little_End
//如果是IP包,就设为0X0800
if(PROTOCOL==IP_PACKED)
ethernet_head.NextProtocal=0X0008;
//如果是ARP包,就设为0X0806
else (PROTOCOL==ARP_PACKED)
ethernet_head.NextProtocal=0X0608;//0X0806;
#endif
#ifdef Big_End
//如果是IP包,就设为0X0800
if(PROTOCOL==IP_PACKED)
ethernet_head.NextProtocal=0X0800;
//如果是ARP包,就设为0X0806
else if(PROTOCOL==ARP_PACKED)
ethernet_head.NextProtocal=0X0806;//0X0806;
#endif
//指向前一个结构数组
PKethernet.STPTR=TxdData;
//ETHERNET报头的长度
PKethernet.length=14;
//ETHERNET报头的指针
PKethernet.DAPTR=(Xuint8*)&ethernet_head;
len=PKethernet.length+TxdData->length;
memcpy(ethernet_frame,PKethernet.DAPTR,PKethernet.length);
memcpy(&ethernet_frame[PKethernet.length],TxdData->DAPTR,TxdData->length);

return(1);
}

3.查找目标IP的MAC地址
Send_Ip_To_LLC()函数为IP数据包的目标IP查找MAC地址,并将数据包发送出去,*TxdData 为发送结构指针;*de_ip为P地址指针。返回值为1,表明操作成功。查询MAC地址时,先在本地缓存表中查询,如果没有找到则发起ARP请求。通过ARP获得相应的MAC地址后,调用以太网发包函数Send_ethernet_Frame()将数据包发送。相应的代码如下:
Xuint8 Send_Ip_To_LLC(Xuint8* TxdData,Xuint8 num) //TxdData 为IP 数据包
{
Xuint8 i;

struct _pkst TxdData1;
TxdData1.length=((eip*)TxdData)->TotalLen;
TxdData1.DAPTR=TxdData;
TxdData1.STPTR=NULL;
Xuint8 de_ip[4];
de_ip[0]=((eip*)TxdData)->DestId[0];
de_ip[1]=((eip*)TxdData)->DestId[1];
de_ip[2]=((eip*)TxdData)->DestId[2];
de_ip[3]=((eip*)TxdData)->DestId[3];

//如果该包在同一网段
//实际应用中可能不解析目的IP地址而直接发送到网关
if((de_ip[0]&NetPort[num].My_Ip_Mark[0])==(NetPort[num].My_Ip[0]
&NetPort[num].My_Ip_Mark[0]))
if((de_ip[1]&NetPort[num].My_Ip_Mark[1])==(NetPort[num].My_Ip[1]
&NetPort[num].My_Ip_Mark[1]))
if((de_ip[2]&NetPort[num].My_Ip_Mark[2])==(NetPort[num].My_Ip[2]
&NetPort[num].My_Ip_Mark[2]))
if((de_ip[3]&NetPort[num].My_Ip_Mark[3])==(NetPort[num].My_Ip[3]
&NetPort[num].My_Ip_Mark[3]))
{
i=0;//查找一次MAC表。
do
{
//如果ARP表的最后两个数相等而且TTL>0表示,有对应的MAC
if(NetPort[num].ARP_TERM[i].IP_NUM[2]==
((IP_NUMBER*)de_ip)->IP[2])
if(NetPort[num].ARP_TERM[i].IP_NUM[3]==
((IP_NUMBER*)de_ip)->IP[3])
if(NetPort[num].ARP_TERM[i].TTL>0)
{
NetPort[num].ARP_TERM[i].TTL=100;//发送
Send_ethernet_Frame(&TxdData1,
NetPort[num].ARP_TERM[i].MAC_NUM,IP_PACKED,num);
return(1);
}
i++;
}
while(i<MAX_ARP_TERM);//如果arp表查完了还没有,就退出
Arp_Request(de_ip,num);//请求对方MAC
return(0);
}
Send_ethernet_Frame(&TxdData1,
NetPort[num].My_Gateway_Mac,IP_PACKED,num);
return(1);
}

4.以太网包接收程序
从功能实现上讲,读取DM9000的接收缓存数据就是接收以太网数据包,因此不需要额外的处理程序,当然如果要解析更高层协议读者可自行加入相关的处理程序。

5.ARP请求和应答
ARP处理包括接收ARP包、解析ARP包以及发出ARP请求这三部分操作,相应的代码如下所列:。
(1)ARP应答函数
Xuint8 Arp_Answer(Xuint8 * ARP_REC_PTR,Xuint8 num)
{
struct _pkst TxdArp;
Xuint8 SEND_ARP_MAC[6];
Xuint8 i;
//如果目标IP地址是本机IP
if (((arp*)ARP_REC_PTR)->DestId[0]==NetPort[num].My_Ip[0])
if (((arp*)ARP_REC_PTR)->DestId[1]==NetPort[num].My_Ip[1])
if (((arp*)ARP_REC_PTR)->DestId[2]==NetPort[num].My_Ip[2])
if (((arp*)ARP_REC_PTR)->DestId[3]==NetPort[num].My_Ip[3])
{
//表示是要解析本地IP的请求
//复制对方IP地址,填充源地址
for(i=0;i<4;i++)
{
SEND_ARP_MAC[i]=((arp*)ARP_REC_PTR)->SourceIp[i];
((arp*)ARP_REC_PTR)->SourceIp[i]=NetPort[num].My_Ip[i];
((arp*)ARP_REC_PTR)->DestId[i]=SEND_ARP_MAC[i];
}
//复制对方物理地址或网关地址
for(i=0;i<6;i++)
{
SEND_ARP_MAC[i]=((arp*)ARP_REC_PTR)->SourceMacId[i];
((arp*)ARP_REC_PTR)->SourceMacId[i]=NetPort[num].My_Mac[i];
((arp*)ARP_REC_PTR)->DestMacId[i]=SEND_ARP_MAC[i];
}
#ifdef Big_End
((arp*)ARP_REC_PTR)->Operation=0x0002; //表明数据帧为ARP应答
#endif
#ifdef Little_End
((arp*)ARP_REC_PTR)->Operation=0x0200; //表明数据帧为ARP应答
#endif
TxdArp.STPTR=NULL;
TxdArp.length=0x1C;
TxdArp.DAPTR=ARP_REC_PTR;
Send_ethernet_Frame(&TxdArp,
SEND_ARP_MAC,ARP_PACKED,num);//发送ARP应答帧
//如果发送方属于本网段
if((((arp*)ARP_REC_PTR)->SourceIp[0]&NetPort[num].My_Ip_Mark[0])==
(NetPort[num].My_Ip[0]&NetPort[num].My_Ip_Mark[0]))
if((((arp*)ARP_REC_PTR)->SourceIp[1]&NetPort[num].My_Ip_Mark[1])==
(NetPort[num].My_Ip[1]&NetPort[num].My_Ip_Mark[1]))
if((((arp*)ARP_REC_PTR)->SourceIp[2]&NetPort[num].My_Ip_Mark[2])==
(NetPort[num].My_Ip[2]&NetPort[num].My_Ip_Mark[2]))
if((((arp*)ARP_REC_PTR)->SourceIp[3]&NetPort[num].My_Ip_Mark[3])==
(NetPort[num].My_Ip[3]&NetPort[num].My_Ip_Mark[3]))
{
//查找有否属于该IP的对应MAC表
for(i=0;i<MAX_ARP_TERM;i++)
{
if(NetPort[num].ARP_TERM[i].IP_NUM[2]==
((arp*)ARP_REC_PTR)->SourceIp[2])
if(NetPort[num].ARP_TERM[i].IP_NUM[3]==
((arp*)ARP_REC_PTR)->SourceIp[3])
if(NetPort[num].ARP_TERM[i].TTL>0)
{
//有则刷新
NetPort[num].ARP_TERM[i].TTL=100;
return(0);
}
}
//查找有否空的MAC表项
for(i=0;i<MAX_ARP_TERM;i++)
{
if(NetPort[num].ARP_TERM[i].TTL==0)
{//有则保存
NetPort[num].ARP_TERM[i].IP_NUM[0]=
((arp*)ARP_REC_PTR)->SourceIp[0];
NetPort[num].ARP_TERM[i].IP_NUM[1]=
((arp*)ARP_REC_PTR)->SourceIp[1];
NetPort[num].ARP_TERM[i].IP_NUM[2]=
((arp*)ARP_REC_PTR)->SourceIp[2];
NetPort[num].ARP_TERM[i].IP_NUM[3]=
((arp*)ARP_REC_PTR)->SourceIp[3];
NetPort[num].ARP_TERM[i].MAC_NUM[0]=
((arp*)ARP_REC_PTR)->SourceMacId[0];
NetPort[num].ARP_TERM[i].MAC_NUM[1]=
((arp*)ARP_REC_PTR)->SourceMacId[1];
NetPort[num].ARP_TERM[i].MAC_NUM[2]=
((arp*)ARP_REC_PTR)->SourceMacId[2];
NetPort[num].ARP_TERM[i].MAC_NUM[3]=
((arp*)ARP_REC_PTR)->SourceMacId[3];
NetPort[num].ARP_TERM[i].MAC_NUM[4]=
((arp*)ARP_REC_PTR)->SourceMacId[4];
NetPort[num].ARP_TERM[i].MAC_NUM[5]=
((arp*)ARP_REC_PTR)->SourceMacId[5];
NetPort[num].ARP_TERM[i].TTL=100;
return(2);
}
}
//MAC表已经满
return(4);
}//IF ARP*/
//不属于同一网段的
return (3);
}
//目标IP不是本机
return (1);
}
(2)ARP包分类处理函数
void PROCESS_ARP_REC(Xuint8 * ARP_PTR,Xuint8 num)
{
// EX_RAM PKST ARP_PACKED;
#ifdef Big_End
if(((arp*)ARP_PTR)->Operation==0X0001)
#endif
#ifdef Little_End
if(((arp*)ARP_PTR)->Operation==0X0100)
#endif
{
Arp_Answer(ARP_PTR,num);
}
#ifdef Big_End
else if(((arp*)ARP_PTR)->Operation==0X0002)
#endif
#ifdef Little_End
else if(((arp*)ARP_PTR)->Operation==0X0200)
#endif
{
REC_ARP_REQ(ARP_PTR,num);
}
//可添加REARP操作。
}
Xuint8 REC_ARP_REQ(Xuint8 * ARP_REC_REQ_PTR,Xuint8 num)
{
Xuint8 i;
//==================================================================
if(((arp*)ARP_REC_REQ_PTR)->SourceIp[0]==NetPort[num].My_Gateway[0])
if(((arp*)ARP_REC_REQ_PTR)->SourceIp[1]==NetPort[num].My_Gateway[1])
if(((arp*)ARP_REC_REQ_PTR)->SourceIp[2]==NetPort[num].My_Gateway[2])
if(((arp*)ARP_REC_REQ_PTR)->SourceIp[3]==NetPort[num].My_Gateway[3])
{ //表示是网关对ARP请求的回答.
for (i=0;i<6;i++)
{
NetPort[num].My_Gateway_Mac[i]=
((arp*)ARP_REC_REQ_PTR)->SourceMacId[i];
}
NetPort[num].Gateway_IP_TTL=100; //表示网关地址已得到解析
return(3);
}

//对方IP即不是本网段也不是GATEWAY
return(1);
}
(3)ARP请求函数
在发出请求时,发送方用目标硬件地址和目标IP地址字段提供目前硬件地址或目标IP地址。其相应的代码如下:
void Arp_Request(Xuint8 * ip_address,Xuint8 num)
{
struct _pkst TxdArpReq;
Xuint8 ARPREQ[46];
Xuint8 i;
for(i=0;i<6;i++)
{
//arp报文的目的物理地址填为0,由arp回答报文 ((arp*)ARPREQ)->SourceMacId[i]=NetPort[num].My_Mac[i];
((arp*)ARPREQ)->DestMacId[i]=0x00; //负责填充
}
for(i=0;i<4;i++)
{
//填充源IP地址
((arp*)ARPREQ)->SourceIp[i]=NetPort[num].My_Ip[i];
//填充目的IP地址
((arp*)ARPREQ)->DestId[i]=*ip_address;
ip_address++;
}
#ifdef Big_End
//硬件类型:0x0001,以太网类型
((arp*)ARPREQ)->HardwareType=0x0001;
//协议类型:0x0800,对应IPv4
((arp*)ARPREQ)->ProtocalType=0x0800;
//操作类型:ARP请求
((arp*)ARPREQ)->Operation=0x0001;
#endif

#ifdef Little_End
//硬件类型:0x0001,以太网类型
((arp*)ARPREQ)->HardwareType=0x0100;
//协议类型:0x0800,对应IPv4
((arp*)ARPREQ)->ProtocalType=0x0008;
//操作类型:ARP请求
((arp*)ARPREQ)->Operation=0x0100;
#endif
//硬件长度:即物理地址长度,单位字节
((arp*)ARPREQ)->HardwareLen=0x06;
//协议长度:即逻辑地址长度,单位字节
((arp*)ARPREQ)->ProtocalLen=0x04;
/************************************* *注意*************************
*arp报文段的长度为28字节,而以太网数据包的最小单元为60字节,所以在发送arp报文时需对以太网报文段进行填充,以满足最小长度要求。可通过dm9000a可以完成此步骤。
*arp分组的封装格式:
*发送报文: | (共14字节) | arp请求分组(28字节) | 填充数据 |
*接收报文: | RTL8019首部 |以太网首部(共14字节) | arp回答分组(28字节) |填充数据 |
* |--> 4字节 <--|------->14字节<---|--> 28字节 <-----|--->18字节<-|
* |--------------------------->0x00~0x<2e<-----------------------------|
*实际上4字节的8019首部在发送时是不起作用的,真正发送的数据从以太网首部起。
*所以在填充数据时,是从0x2e开始的。
**********************************************************************/
TxdArpReq.STPTR=NULL;
TxdArpReq.length=46;
TxdArpReq.DAPTR=ARPREQ;
Xuint8 ARP_MAC[6]=ARP_REP_MAC;

Send_ethernet_Frame(&TxdArpReq,ARP_MAC,ARP_PACKED,num);
}
(4)ARP初始化程序
void Initial_arp(Xuint8 num)
{
Xuint8 i;

NetPort[num].Gateway_IP_TTL=0;
for(i=0;i<MAX_ARP_TERM;i++)
{
NetPort[num].ARP_TERM[i].TTL=0;
}
Arp_Request(NetPort[num].My_Gateway,num);
}

4.简易的入口测试程序
为了便于读者测试,下面给出一个简单的入口测试程序,将自然数通过DM9000发送出去,其相应的代码如下:
#include "Xgpio.h"
#include "dm9000a_l.h"
#include "xio.h"

#define LED_0 8 //定义LED标号
#define LED_1 4
#define LED_2 2
#define LED_3 1

Xuint8 data_buf_0[MAX_PACKET_SIZE];
Xuint8 data_buf_1[MAX_PACKET_SIZE];
Xuint16 rx_len_0[1]={1500};
Xuint16 rx_len_1[1];
Xuint16 i,num;

Xuint8 sim_PC_MAC[6]={0x01,0x02,0x03,0x04,0x05,0x06};
num = 1;
int main()
{
XGpio LEDs;
XGpio_Initialize(&LEDs, XPAR_LEDS_DEVICE_ID);
XGpio_SetDataDirection(&LEDs, 1, 0x11111111);
//XGpio_DiscreteWrite(&LEDs, 1, LED_0);
msleep(10);
SetNetPort();
if(DM9000_init(ETH_Port_0,0)==0)
{
//XGpio_DiscreteWrite(&LEDs, 1, LED_1);
}
Initial_arp(0);
while(num >0)
{
num--;
XGpio_DiscreteWrite(&LEDs, 1, LED_1|LED_2);
XGpio_DiscreteWrite(&LEDs, 1, LED_0);
XGpio_DiscreteWrite(&LEDs, 1, LED_2);
XGpio_DiscreteWrite(&LEDs, 1, LED_2);
//初始化发送缓冲
for(i=0;i<256;i++)
data_buf_0[i]= i;
//直接利用DM9000发送出去
TransmitPacket(ETH_Port_0,data_buf_0,256);
//重新初始化发送数据
for(i=0;i<256;i++)
data_buf_0[i]=255-i;
//将数据组成以太网帧发送出去
Send_ethernet_Frame(data_buf_0,sim_PC_MAC,0x03,0);
}
}

上述代码可基本实现以太网接口,读者可在此基础上开发更高层协议,完成TCP/UPD以及IP层协议,并开发诸如Web等应用协议。

 

<上一节 

相关链接


 
关于我们 | 诚邀加盟 | 客户服务 | 相关法律 | 网站地图 | 友情链接 | 服务信箱:service@eefocus.com
© 2006 与非门科技信息咨询(北京)有限公司 All Rights Reserved.