DM9000 PHY 1.DM9000控制器的实现 从DataSheet已经明确,DM9000提供了通用的CPU接口,简化了与MirRoBlaze硬件接口的实现过程,可直接通过EDK库中的EPC(外设控制器)核控制DM9000。
DM9000可利用EDK提供的xps_epc核来实现,该核的时序完全满足DM9000的要求,相应的控制信号可由Utility Vector Logic和Utility Bus Split核来完成。DM9000控制器的整体结构如图1-1所示。
图1-1 XPS EPC核与DM9000的整体连接结构
将XPS_EPC核添加到系统中,并连接在PLB总线上,其中XPS_EPC核的配置具体连接关系如图1-2所示,最大地址宽度设置为4,数据位宽设置为16,其相应的端口按照图1-1连接即可,除了地址总线(PRH_ddr)外,其余的端口需要设置成外部端口。
图1-2EPC核的配置参数示意图
图1-1中外部逻辑涉及两个操作:总线分割和与、非等逻辑电平转换操作。总线分割利用EDK自带的Utility Bus Split核完成,该核的功能如图1-3所示,将输入总线分割为位宽更窄的1个或多个独立总线。
图1-3 DM9000控制器外设的整体实现
逻辑转换利用Utility Vector Logic核来实现,其配置界面如5-16所示,“Type of Vector Operation To Perform”下拉框中可选择与、或、异或和同或等操作;“Size of The Vector”文本框用于输入信号位宽。本设计只用到与、非操作。
图1-4 Utility Vector Logic核参数配置界面
外部逻辑主要将EPC外设的地址线最低位分割出来得到dm9000a_0_split_Out1信号,并将EPC核的片选信号CS取反得到dm9000a_0_not_Res信号,再将dm9000a_0_split_Out1和dm9000a_0_not_Res相与得到信号dm9000a_0_and_Res,再取反得到dm9000a_0_not_cmd_Res信号,并直接将其设置为外部端口连接到DM9000的CMD管脚上。上述过程的信号操作如图1-5到1-8所示。
图1-5 总线分割核连接示意图
图1-6 "非"逻辑核连接关系示意图
图1-7 "与"逻辑核变换连接关系示意图
图1-8 "非"逻辑核变换连接关系示意图
2.DM9000的软件驱动 要在MirRoBlaze系统中以DM9000芯片作为网络芯片,首先要通过地址端口和数据端口来配置DM9000芯片,使其能够完成设计人员定义的功能。要进行数据收发所必须实现的几个功能包括: (1)DM9000端口读、写操作 在地址端口被赋值之后,对数据端口的操作就会完全反映到相应的片内内存空间中,这相当于对DM9000片内的地址空间进行了一次映射,使得只通过两个端口就可以实现对DM9000片内所有寄存器的访问操作,节省了DM9000所占用的系统总线的地址空间。图1-9给出了寄存器读写过程示意图。
图1-9 寄存器读写示意图
寄存器读操作的软件实现 DM9000寄存器的读操作就是直接访问该地址,EDK提供了XIo_In32、XIo_In16以及XIo_In8这三个函数来读取给定地址的数据,其本质都是简单的指针获取函数,只是数据位宽分别为32比特、16比特和8比特。例如,XIo_In16的原型如下所列:
#define XIo_In16(InputPtr) (*(volatile Xuint16 *)(InputPtr))
由于EPC核选择了16比特,因此在XIo_In16函数基础上进行封装,形成ior函数,其中BaseAddress为DM9000在Microblaze系统中的基地址,reg为偏移地址。由于访问DM9000内部寄存器,需要将地址写入相应的接口寄存器,因此先调用Dm9000_Write()函数写入地址,再读取DM9000的数据接口。相应的代码如下: Xuint16 ior(Xuint32 BaseAddress, Xuint16 reg) { Dm9000_Write(BaseAddress,IO_addr,reg); return Dm9000_Read(BaseAddress,IO_data); } Xuint16 Dm9000_Read(Xuint32 BaseAddress,int type) { return XIo_In16(BaseAddress+type); } 寄存器写操作的软件实现 寄存器写操作就是指针赋值,和读取类似,EDK提供了XIo_Out32、XIo_ Out16以及XIo_Out8这三个函数来读取给定地址的数据,其本质也都一样,只是数据位宽不同。例如XIo_ Out16的原型如下:
#define XIo_Out16(OutputPtr, Value) \ *(volatile Xuint16 *)((OutputPtr)) = (Value))
同样,由于EPC核选择了16比特,因此在XIo_Out16函数基础上进行封装,形成iow函数,先写入地址,再写入数据。相应的代码如下:
void iow(Xuint32 BaseAddress,Xuint16 reg, Xuint16 data) { Dm9000_Write(BaseAddress,IO_addr,reg); Dm9000_Write(BaseAddress,IO_data,data); } void Dm9000_Write(Xuint32 BaseAddress,int type,Xuint16 Value) { XIo_Out16(BaseAddress+type, Value); }
(2)PHY端口配置寄存器 PHY寄存器就是物理层配置寄存器,3个常用的寄存器如表1-1所列。
表1-1 常用的PHY寄存器
配置这些物理寄存器的方法为:首先将寄存器地址写到EPAR寄存器中(OCH),注意要将寄存器地址的第6位置1(地址与0x40进行或运算),表明写的是PHY地址,而不是EEPROM地址;其次,将数据的高字节写到PHY_DRH寄存器(0EH)中;第三,将数据的低字节写到PHY_DRL寄存器(0EH)中;第四,发送PHY写命令(0x0A)到EPCR寄存器(0BH)中;最后,延迟一段时间(1~30 us),发送0x08到EPCR寄存器,清除PHY写命令。相应的配置PHY寄存器的软件代码为: void phy_write (Xuint32 BaseAddress,Xuint16 reg, Xuint16 value) { /* set PHY register address into EPAR REG. 0CH */ iow(BaseAddress,0x0C, reg | 0x40); /* PHY register address setting, and DM9000_PHY offset = 0x40 */
/* fill PHY WRITE data into EPDR REG. 0EH & REG. 0DH */ /* PHY data high_byte */ iow(BaseAddress,0x0E, ((value >> 8) & 0xFF)); /* PHY data low_byte */ iow(BaseAddress,0x0D, value & 0xFF);
/* issue PHY + WRITE command = 0xa into EPCR REG. 0BH */ /* clear PHY command first */ iow(BaseAddress,0x0B, 0x8); /* issue PHY + WRITE command */ Dm9000_Write(BaseAddress, IO_data, 0x0A); usleep(STD_DELAY); /* clear PHY command again */ Dm9000_Write(BaseAddress, IO_data, 0x08); /* wait 1~30 us (>20 us) for PHY + WRITE completion */ usleep(30); } 其中的usleep()函数为微秒延迟函数,可通过for循环实现,其相应的代码为: void usleep(Xuint16 count) //1us stepwise { Xuint16 i,j; for(i=1;i<=count;i++) { for(j=0;j<=10;j++); //需要经过计算获得 } } 复位与初始化的实现 复位与初始化功能是芯片必须要实现的一个基本功能,在系统启用了网络芯片之后,就要对网络芯片进行基本的配置,设置网络芯片的寄存器,使网络芯片达到工作状态;同样在检测到网络芯片处于非正常状态时就要复位网络芯片并重新进行配置,使得网络芯片从未知状态恢复,实现方法如下: 初始化时要进行以下的操作来使DM9000芯片达到工作状态:
开启DM9000芯片,DM9000芯片上电默认的状态是GEPIO0=1的powerdown状态,所以要将GEPIO0 bit[0]设置为0,来打开DM9000芯片。上电之后将GPCR(REG_1E) GEP_CNTL0 bit[0]设置为1;同时将GPR(REG_1F)GEPIO0 bit[0]设置为0来打开芯片;
进行两次软启动,根据芯片的设计要求要使芯片达到工作状态,在上电之后就要对芯片进行两次软启动,软启动是通过设置NCR(REG_00)bit[2:0]=011(至少20 us),设置NCR(REG_00)bit[2:0]=000来实现的,同样的操作要进行两次;
清除Tx Status寄存器; 设置IMR(REG_FF)寄存器PRM bit[0]/PTM bit[1],开启TX/RX中断; 设置RCR寄存器来使能RX。Rx功能函数的使能是设置RX控制寄存器(REG_05)RXEN bit[0]=1。 当进行了以上的步骤之后,DM9000芯片就处于工作状态。当由于异常而引发重启时就要再次进行相同的操作过程来使DM9000芯片恢复到正常状态。在初始化DM9000芯片时将设置芯片中的控制寄存器,例如与接收任务相关的接收任务控制寄存器(REG_05H),其各个标志位的功能如表5-4所示。RX控制寄存器是接收任务所要用到的最重要的寄存器,其中包含了设置接收使能的RX使能位(bit0 RXEN),可以通过设置这一位来实现对接收任务的控制,在禁用芯片的时候要将RX使能位清除。Bit[4:1]用来设置对接收包的限制,当有不符合设置的包到达时将自动丢弃接收包,不产生中断。
表1-2 接收控制寄存器
DM9000的初始化软件代码为: Xuint16 DM9000_init (Xuint32 BaseAddress,Xuint8 Num) /* initialize DM9000 LAN chip */ { Xuint16 i; Xuint8 ether_addr0[6]=My_Mac_1ID; Xuint8 ether_addr1[6]=My_Mac_2ID; /* set the internal PHY power-on (GPIOs normal settings) */ iow(BaseAddress,0x1E, 0x01); /* GPCR REG. 1EH = 1 selected GPIO0 "output" port for internal PHY */ iow(BaseAddress,0x1F, 0x00); /* GPR REG. 1FH GEPIO0 Bit [0] = 0 to activate internal PHY */ /* wait > 2 ms for PHY power-up ready */ msleep(2);
/* software-RESET NIC */ iow(BaseAddress,NCR, 0x03); /* NCR REG. 00 RST Bit [0] = 1 reset on, and LBK Bit [2:1] = 01b MAC loopback on */ /* wait > 10us for a software-RESET ok */ usleep(10); /* normalize */ iow(BaseAddress,NCR, 0x00); iow(BaseAddress,NCR, 0x03); usleep(10); iow(BaseAddress,NCR, 0x00);
/* set GPIO0=1 then GPIO0=0 to turn off and on the internal PHY */ /* GPR PHYPD Bit [0] = 1 turn-off PHY */ iow(BaseAddress,0x1F, 0x01); /* PHYPD Bit [0] = 0 activate phyxcer */ iow(BaseAddress,0x1F, 0x00); /* wait >4 ms for PHY power-up */ msleep(4);
/* set PHY operation mode */ /* reset PHY: registers back to the default states */ phy_write(BaseAddress,0,PHY_reset); /* wait >30 us for PHY software-RESET ok */ usleep(30); /* turn off PHY reduce-power-down mode only */ phy_write(BaseAddress,16, 0x404); phy_write(BaseAddress,4, PHY_txab); /* set PHY TX advertised ability: ALL + Flow_control */ phy_write(BaseAddress,0, 0x1200); /* PHY auto-NEGO re-start enable (RESTART_AUTO_NEGOTIATION + AUTO_NEGOTIATION_ENABLE) to auto sense and recovery PHY registers */ /* wait >2 ms for PHY auto-sense linking to partner */ msleep(2);
/* store MAC address into NIC */ if(Num==0) { for (i = 0; i < 6; i++) iow(ETH_Port_0,16 + i, ether_addr0); } else if (Num==1) { for (i = 0; i < 6; i++) iow(ETH_Port_1,16 + i, ether_addr1); }
/* clear any pending interrupt */ iow(BaseAddress,ISR, 0x3F); /* clear the ISR status: PRS, PTS, ROS, ROOS 4 bits, by RW/C1 */ iow(BaseAddress,NSR, 0x2C); /* clear the TX status: TX1END, TX2END, WAKEUP 3 bits, by RW/C1 */
/* program operating registers~ */ iow(BaseAddress,NCR, NCR_set); /* NCR REG. 00 enable the chip functions (and disable this MAC loopback mode back to normal) */ iow(BaseAddress,0x08, BPTR_set); /* BPTR REG.08 (if necessary) RX Back Pressure Threshold in Half duplex moe only: High Water 3KB, 600 us */ iow(BaseAddress,0x09, FCTR_set); /* FCTR REG.09 (if necessary) Flow Control Threshold setting High/ Low Water Overflow 5KB/ 10KB */ iow(BaseAddress,0x0A, RTFCR_set); /* RTFCR REG.0AH (if necessary) RX/TX Flow Control Register enable TXPEN, BKPM (TX_Half), FLCE (RX) */ iow(BaseAddress,0x0F, 0x00); /* Clear the all Event */ iow(BaseAddress,0x2D, 0x80); /* Switch LED to mode 1 */
/* set other registers depending on applications */ iow(BaseAddress,ETXCSR, ETXCSR_set); /* Early Transmit 75% */
/* enable interrupts to activate DM9000 ~on */ iow(BaseAddress,IMR, INTR_set); /* IMR REG. FFH PAR=1 only, or + PTM=1& PRM=1 enable RxTx interrupts */
/* enable RX (Broadcast/ ALL_MULTICAST) ~go */
iow(BaseAddress,RCR , RCR_set | RX_ENABLE | PASS_MULTICAST); /* RCR REG. 05 RXEN Bit [0] = 1 to enable the RX machine/ filter */ /* RETURN "DEVICE_SUCCESS" back to upper layer */ return (ior(BaseAddress,0x2D)==0x80) ? DMFE_SUCCESS : DMFE_FAIL;
} (5)接收数据包的操作实现 数据包接收功能是DM9000芯片实现网络功能的基础,在接收数据时可以采用中断方式,也可以采用轮询方式。当采用中断方式时,数据到达且在DM9000内部CRC校验通过后会产生一个接收中断,中断发生时可以将DM9000所接收到的数据包读出并交由上层协议进行处理。采用轮询方式将以固定的周期收取数据。本设计由于外围设备很少,因此采用了轮询机制。 接收到的数据在经过硬件部分的CRC校验之后存放在RX FIFO中,在DM9000中的内部地址0x0C00-0x3FFF(13K byte)。在每一个接收到的数据包的前面都有一个4bytes的头,可以用MRCMDX(REG_F0)和MRCMD(REG_F2)寄存器来读取接收到的数据包的信息。接收数据在接收缓冲中的结构如图1-10所示。
图1-10 接收数据缓存结构示意图
其中第一个字节是接收数据标志,通过读取这一位来判断是否有数据到达,如果这一位是01则表示有数据被接收且保存到RX SRAM中,这时候可以将数据读出并进行处理;如果既不是01又不是00则认为有异常发生,这时就要将DM9000芯片重启以使芯片恢复到正常状态。第二个字节是status,这个字节是接收数据包的状态字,其中的内容与接收状态寄存器(REG_06H)中的内容相同。可以用来判断所接收的数据包是否正常,或发生了何种异常,这样就可以针对不同的异常进行不同的操作,实现对接收任务的控制。第3字节、第4字节存有接收到的数据包的长度,在读取数据包的时候要用这个长度来进行控制。这四个字节的包头是DM9000在接收数据的时候添加的信息,不属于数据包的内容。从第5个字节开始的数据才是真正的数据包的内容,其长度在第3、4字节中定义,所以驱动程序可以依次读取数据包数据,直至达到数据包长度为止。相应的DM9000接收数据包代码如下:
Xuint16 ReceivePacket (Xuint32 BaseAddress,Xuint8 *data_ptr,Xuint16 *rx_len) { Xuint8 rx_READY,GoodPacket; Xuint16 Tmp, RxStatus, i;
RxStatus = rx_len[0] = 0; GoodPacket=FALSE;
/* mask NIC interrupts IMR: PAR only */ iow(BaseAddress,IMR, PAR_set);
/* dummy read a byte from MRCMDX REG. F0H */ rx_READY = ior(BaseAddress,MRCMDX);
/* got most updated byte: rx_READY */ rx_READY = Dm9000_Read(BaseAddress,IO_data)&0x03; //usleep(STD_DELAY);
/* check if (rx_READY == 0x01): Received Packet READY? */ if (rx_READY == DM9000_PKT_READY) { /* got RX_Status & RX_Length from RX SRAM */ Dm9000_Write(BaseAddress, IO_addr, MRCMD); /* set MRCMD REG. F2H RX I/O port ready */ //usleep(STD_DELAY); RxStatus = Dm9000_Read(BaseAddress,IO_data); //usleep(STD_DELAY); rx_len[0] = Dm9000_Read(BaseAddress,IO_data);
/* Check this packet_status GOOD or BAD? */ if ( !(RxStatus & 0xBF00) && (rx_len[0] < MAX_PACKET_SIZE) ) { /* read 1 received packet from RX SRAM into RX buffer */ for (i = 0; i < rx_len[0]; i += 2) { //usleep(STD_DELAY); Tmp = Dm9000_Read(BaseAddress, IO_data); data_ptr = Tmp&0xFF; data_ptr[i+1] = (Tmp>>8)&0xFF; } GoodPacket=TRUE; } /* end if (GoodPacket) */ else { /* this packet is bad, dump it from RX SRAM */ for (i = 0; i < rx_len[0]; i += 2) { //usleep(STD_DELAY); Tmp = Dm9000_Read(BaseAddress, IO_data); } rx_len[0] = 0; } /* end if (!GoodPacket) */ } /* end if (rx_READY == DM9000_PKT_READY) ok */ else if(rx_READY) /* status check first byte: rx_READY Bit[1:0] must be "00"b or "01"b */ { /* software-RESET NIC */ /* NCR REG. 00 RST Bit [0] = 1 reset on, and LBK Bit [2:1] = 01b MAC loopback on */ iow(BaseAddress,NCR, 0x03); /* wait > 10us for a software-RESET ok */ usleep(10); /* normalize */ iow(BaseAddress,NCR, 0x00); iow(BaseAddress,NCR, 0x03); usleep(10); iow(BaseAddress,NCR, 0x00); /* program operating registers~ */ /* NCR REG. 00 enable the chip functions (and disable this MAC loopback mode back to normal) */ iow(BaseAddress,NCR, NCR_set); /* BPTR REG.08 (if necessary) RX Back Pressure Threshold in Half duplex moe only: High Water 3KB, 600 us */ iow(BaseAddress,0x08, BPTR_set); /* FCTR REG.09 (if necessary) Flow Control Threshold setting High/ Low Water Overflow 5KB/ 10KB */ iow(BaseAddress,0x09, FCTR_set); /* RTFCR REG.0AH (if necessary) RX/TX Flow Control Register enable TXPEN, BKPM (TX_Half), FLCE (RX) */ iow(BaseAddress,0x0A, RTFCR_set); /* Clear the all Event */ iow(BaseAddress,0x0F, 0x00); /* Switch LED to mode 1 */ iow(BaseAddress,0x2D, 0x80); /* set other registers depending on applications */ /* Early Transmit 75% * iow(BaseAddress,ETXCSR, ETXCSR_set); / /* enable interrupts to activate DM9000 ~on */ /* IMR REG. FFH PAR=1 only, or + PTM=1& PRM=1 enable RxTx interrupts */ iow(BaseAddress,IMR, INTR_set); /* enable RX (Broadcast/ ALL_MULTICAST) ~go */ /* RCR REG. 05 RXEN Bit [0] = 1 to enable the RX machine/ filter */ iow(BaseAddress,RCR , RCR_set | RX_ENABLE | PASS_MULTICAST); } /* end NIC H/W system Data-Bus error */
return GoodPacket ? DMFE_SUCCESS : DMFE_FAIL; } (6)发送数据包的操作流程 数据包发送功能的实现主要是依靠DM9000中的发送数据缓存区,驱动函数将要发送的数据包存入到发送数据缓存区内之后,设置发送标志来进行数据的发送。图1-11给出了数据包发送任务的流程图。
图1-11 数据包发送流程图
首先,检测工作模式; 第二步,将要发送的数据包写入发送数据缓冲区; 第三步,将要发送的数据先写入发送数据长度的高字节,再写入发送数据包长度的低字节; 第四步,设置传送标志。 设置完传送标志之后,DM9000就会将存入发送数据缓冲区的数据发送出去。DM9000对数据的发送要求驱动程序将数据写入DM9000芯片中的发送数据缓冲区中去。DM9000在接收到处理器芯片发来的数据发送标志之后,就会将写入缓冲区的数据包处理成Ethernet数据包的形式进行发送,在发送过程中还可以继续接收数据包的写入,所以在芯片中就分了两个逻辑上的发送数据包,分别有两组状态寄存器和控制寄存器来实现发送的控制及状态检查工作。其相应的软件代码如下: Xuint16 TransmitPacket(Xuint32 BaseAddress,Xuint8 *data_ptr,Xuint16 tx_len) { Xuint16 i;
/* mask NIC interrupts IMR: PAR only */ iow(BaseAddress,IMR, PAR_set);
/* issue TX packet's length into TXPLH REG. FDH & TXPLL REG. FCH */ /* TXPLH High_byte length */ iow(BaseAddress,0xFD, (tx_len >> 8) & 0xFF); /* TXPLL Low_byte length */ iow(BaseAddress,0xFC, tx_len & 0xFF);
/* wirte transmit data to chip SRAM */ /* set MWCMD REG. F8H TX I/O port ready */ Dm9000_Write(BaseAddress, IO_addr, MWCMD); for (i = 0; i < tx_len; i += 2) { //usleep(STD_DELAY); Dm9000_Write(BaseAddress, IO_data, (data_ptr[i+1]<<8)|data_ptr ); } /* issue TX polling command activated */ /* TXCR Bit [0] TXREQ auto clear after TX completed */ iow(BaseAddress,TCR , TCR_set | TX_REQUEST);
/* wait TX transmit done */ while(!(ior(BaseAddress,NSR)&0x0C)) // usleep(STD_DELAY);
/* clear the NSR Register */ iow(BaseAddress,NSR,0x00);
/* re-enable NIC interrupts */ iow(BaseAddress,IMR, INTR_set);
/* RETURN "TX_SUCCESS" to upper layer */ return DMFE_SUCCESS; } Microblaze 以太网 完成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*)ðernet_head; len=PKethernet.length+TxdData->length; memcpy(ethernet_frame,PKethernet.DAPTR,PKethernet.length); memcpy(ðernet_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.IP_NUM[2]== ((IP_NUMBER*)de_ip)->IP[2]) if(NetPort[num].ARP_TERM.IP_NUM[3]== ((IP_NUMBER*)de_ip)->IP[3]) if(NetPort[num].ARP_TERM.TTL>0) { NetPort[num].ARP_TERM.TTL=100;//发送 Send_ethernet_Frame(&TxdData1, NetPort[num].ARP_TERM.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=((arp*)ARP_REC_PTR)->SourceIp; ((arp*)ARP_REC_PTR)->SourceIp=NetPort[num].My_Ip; ((arp*)ARP_REC_PTR)->DestId=SEND_ARP_MAC; } //复制对方物理地址或网关地址 for(i=0;i<6;i++) { SEND_ARP_MAC=((arp*)ARP_REC_PTR)->SourceMacId; ((arp*)ARP_REC_PTR)->SourceMacId=NetPort[num].My_Mac; ((arp*)ARP_REC_PTR)->DestMacId=SEND_ARP_MAC; } #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.IP_NUM[2]== ((arp*)ARP_REC_PTR)->SourceIp[2]) if(NetPort[num].ARP_TERM.IP_NUM[3]== ((arp*)ARP_REC_PTR)->SourceIp[3]) if(NetPort[num].ARP_TERM.TTL>0) { //有则刷新 NetPort[num].ARP_TERM.TTL=100; return(0); } } //查找有否空的MAC表项 for(i=0;i<MAX_ARP_TERM;i++) { if(NetPort[num].ARP_TERM.TTL==0) {//有则保存 NetPort[num].ARP_TERM.IP_NUM[0]= ((arp*)ARP_REC_PTR)->SourceIp[0]; NetPort[num].ARP_TERM.IP_NUM[1]= ((arp*)ARP_REC_PTR)->SourceIp[1]; NetPort[num].ARP_TERM.IP_NUM[2]= ((arp*)ARP_REC_PTR)->SourceIp[2]; NetPort[num].ARP_TERM.IP_NUM[3]= ((arp*)ARP_REC_PTR)->SourceIp[3]; NetPort[num].ARP_TERM.MAC_NUM[0]= ((arp*)ARP_REC_PTR)->SourceMacId[0]; NetPort[num].ARP_TERM.MAC_NUM[1]= ((arp*)ARP_REC_PTR)->SourceMacId[1]; NetPort[num].ARP_TERM.MAC_NUM[2]= ((arp*)ARP_REC_PTR)->SourceMacId[2]; NetPort[num].ARP_TERM.MAC_NUM[3]= ((arp*)ARP_REC_PTR)->SourceMacId[3]; NetPort[num].ARP_TERM.MAC_NUM[4]= ((arp*)ARP_REC_PTR)->SourceMacId[4]; NetPort[num].ARP_TERM.MAC_NUM[5]= ((arp*)ARP_REC_PTR)->SourceMacId[5]; NetPort[num].ARP_TERM.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= ((arp*)ARP_REC_REQ_PTR)->SourceMacId; } 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=NetPort[num].My_Mac; ((arp*)ARPREQ)->DestMacId=0x00; //负责填充 } for(i=0;i<4;i++) { //填充源IP地址 ((arp*)ARPREQ)->SourceIp=NetPort[num].My_Ip; //填充目的IP地址 ((arp*)ARPREQ)->DestId=*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.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; //直接利用DM9000发送出去 TransmitPacket(ETH_Port_0,data_buf_0,256); //重新初始化发送数据 for(i=0;i<256;i++) data_buf_0=255-i; //将数据组成以太网帧发送出去 Send_ethernet_Frame(data_buf_0,sim_PC_MAC,0x03,0); } }
上述代码可基本实现以太网接口,根据情况,在此基础上开发更高层协议,完成TCP/UPD以及IP层协议,并开发诸如Web等应用协议。
|