网卡概述
OK6410开发板上集成一个100M以太网接口,通过DM9000AE芯片来扩展。在开发过程中,以太网可以用来进行下载镜像,挂载NFS文件系统,或是开发网络应用程序等。使用时,如果是直接连接PC机网口,一般使用交叉网线,而通过交换机或路由器进行连接时,则使用直通网线。
网卡本身工作在OSI七层模型中的最低两层:物理层和数据链接路层。这两层之上是网络层,对应的主要协议是IP协议和ARP协议,网卡即是把来自于网络层的数据进行封装,按照数据链接路层的要求加上MAC地址和对应的协议类型及校验字段,然后从网卡物理接口上发出去。所以在使用网卡发送收发数据时,每个层的封装都需要自己来组装,典型的以太网封装包括以太网头,IP头,UDP或TCP头,然后才是用户数据。在这一点上和原始套接字是类似的,在使用原始套接字时,用户也需要自己组装数据包,然后指定一个网络接口进行发送数据。
网卡一般由PHY芯片和MAC芯片两大部分组成,它们之间通过MII(介质独立接口)进行连接和通信。PHY和MAC一般是独立的,但也可以集成在一块芯片内部,比如DM9000AE芯片就是把PHY和MAC集成在一起的。关于MAC/PHY/MII的介绍在这篇帖子有比较详细的介绍关于网卡及MAC和PHY的区别,讲的比较清楚(转帖).总得来说就是,MAC用于将上层的数据包组装成符合数据链路层格式的帧,比如控制帧的大小,添加源MAC地址和目的MAC地址,添加校验信息等,然后PHY负责将这个帧从物理接口上发出去,具体的物理接口可以是以太网口,光纤接口,或是无线网络接口等。网卡的实质就是MAC通过MII控制PHY的过程。
DM9000初始化
这部分需要同时阅读DM9000的Datasheet和Application Note。DM9000的初始化包含内存bank的配置,芯片初始化,填充MAC地址,以及使能芯片工作等。所有的配置都是通过填充DM9000的相关寄存器内容来完成的,而DM9000是一块单独的芯片,并不在ARM主芯片的内部,所以填写这些寄存器只能通过DM9000提供的两个寄存器端口来完成。这两个端口需要通过DM9000的电路连接来确定,一个用于传输DM9000内部寄存器的地址,另一个则用于读写数据。
具体的初始化流程可以参考UBOOT中DM9000的驱动部分,以下是可用的初始化代码:
void eth_init()
{
int i, oft;
u32 ID;
/*内存bank初始化,DM9000使用内存bank1*/
cs_init();
/*复位设备*/
dm9000_reset();
/*查找DM9000设备ID*/
ID = get_DM9000_ID();
if(ID != DM9000_ID)
{
printf("not found the dm9000 ID:%x\r\n",ID);
return;
}
printf("Found DM9000 ID:%X at address %x !\r\n", ID, DM9000_BASE);
/* Program operating register, only internal phy supported */
DM9000_iow(DM9000_NCR, 0x0);
/* TX Polling clear */
DM9000_iow(DM9000_TCR, 0);
/* Less 3Kb, 200us */
DM9000_iow(DM9000_BPTR, BPTR_BPHW(3) | BPTR_JPT_600US);
/* Flow Control : High/Low Water */
DM9000_iow(DM9000_FCTR, FCTR_HWOT(3) | FCTR_LWOT(8));
/* SH FIXME: This looks strange! Flow Control */
DM9000_iow(DM9000_FCR, 0x0);
/* Special Mode */
DM9000_iow(DM9000_SMCR, 0);
/* clear TX status */
DM9000_iow(DM9000_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
/* Clear interrupt status */
DM9000_iow(DM9000_ISR, ISR_ROOS | ISR_ROS | ISR_PTS | ISR_PRS);
/*填充MAC地址*/
/* fill device MAC address registers */
for (i = 0, oft = DM9000_PAR; i < 6; i++, oft++)
DM9000_iow(oft, mac_addr[i]);
for (i = 0, oft = 0x16; i < 8; i++, oft++)
DM9000_iow(oft, 0); /*将广播地址设为0,避免接收多播和广播包*/
/*激活DM9000*/
/* RX enable */
DM9000_iow(DM9000_RCR, RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN);
/* Enable TX/RX interrupt mask */
DM9000_iow(DM9000_IMR, IMR_PAR);
return;
}
DM9000数据收发
DM9000数据收发函数同样参考UBOOT进行设计。注意一点,DM9000的收发都可以产生外部中断,我们可能会理所当然地使用中断来简化程序的设计,但实际使用中,uboot代码并未使用这些中断,而是用轮询的方式进行数据的收发(相关的UBOOT函数是NetLoop)。这样的设计是为了减轻系统的负担,因为使用中断的话,任何到来的数据包都需要处理,这对于CPU来说其实是不必的。正确的操作方式是,只在有网络需求的时候才进行网络收发,没有网络需要时则将网卡关闭。这时使用轮询的优点就体现出来了,以UBOOT内的tftp下载实现为例,UBOOT输入tftp进行下载时,首先执行DM9000初始化函数,打开网卡,然后发送tftp请求,接下来进入一个for(;;)循环,使用轮询的方式进行tftp数据包的接收,每接收一个就进行一次响应,直到最后一个包完成,然后设置相关的标志位,for循环内部检测到这个标志位后即跳出循环,最后关闭网卡。
int eth_send(void *packet, u32 length)
{
/*禁止中断*/
DM9000_iow(DM9000_IMR, 0x80);
//DM9000_iow(DM9000_ISR, IMR_PTM); /* Clear Tx bit in ISR */
/*写入发送数据的长度*/
/* Set TX length to DM9000 */
DM9000_iow(DM9000_TXPLL, length & 0xff);
DM9000_iow(DM9000_TXPLH, (length >> 8) & 0xff);
/*写入要发送的数据*/
/* Move data to DM9000 TX RAM */
DM9000_outb(DM9000_MWCMD, DM9000_IO); /* Prepare for TX-data */
/* push the data to the TX-fifo */
dm9000_outblk_16bit(packet, length);
/*启动发送*/
/* Issue TX polling command */
DM9000_iow(DM9000_TCR, TCR_TXREQ); /* Cleared after TX complete */
/*等待发送结束*/
while(1)
{
u8 status = DM9000_ior(DM9000_TCR);
//printf("sending data...\r\n");
if(!(status & 0x1))
break;
}
/*清除发送状态*/
DM9000_iow(DM9000_NSR, 0x2c);
//DM9000_iow(DM9000_ISR, IMR_PTM); /* Clear Tx bit in ISR */
/*恢复中断*/
DM9000_iow(DM9000_IMR, 0x81);
//printf("eth_send done\r\n");
return 1;
}
u16 eth_rx(void)
{
u16 status,len;
u16 tmp;
u32 i;
u8 ready = 0;
/*判断是否中断,如果产生则清除中断*/
if (DM9000_ior(DM9000_ISR) & 0x01) /* Rx-ISR bit must be set. */
{
DM9000_iow(DM9000_ISR, 0x01);
}
else
{
return 0;
}
/*空读*/
DM9000_outb(DM9000_MRCMDX, DM9000_IO);
ready = DM9000_inw(DM9000_DATA);
//ready = DM9000_ior(DM9000_MRCMDX); /* Dummy read */
if((ready & 0x01) != 0x01)
{
DM9000_outb(DM9000_MRCMDX, DM9000_IO);
ready = DM9000_inw(DM9000_DATA);
//ready = DM9000_ior(DM9000_MRCMDX); /* Dummy read */
if((ready & 0x01) != 0x01)
{
printf("dummy read error\r\n");
return 0;
}
}
/*读取状态*/
//status = DM9000_ior(DM9000_MRCMD);
DM9000_outb(DM9000_MRCMD, DM9000_IO);
status = DM9000_inw(DM9000_DATA);
/*读取包的长度*/
//len = DM9000_ior(DM9000_MRCMD);
DM9000_outb(DM9000_MRCMD, DM9000_IO);
len = DM9000_inw(DM9000_DATA);
//printf("packet length:%d\r\n", len);
if((status & 0xbf00) || (len < 0x40) || (len > DM9000_PKT_MAX))
{
if (status & 0x100)
{
printf("rx fifo error\n");
}
if (status & 0x200) {
printf("rx crc error\n");
}
if (status & 0x8000) {
printf("rx length error\n");
}
if (len > DM9000_PKT_MAX) {
printf("rx length too big\n");
//dm9000_reset();
}
}
for(i = 0; i < len; i +=2)
{
tmp = DM9000_inw(DM9000_DATA);
buffer[i] = tmp & 0xff;
buffer[i+1] = (tmp>>8) & 0xff;
}
net_receive(&buffer[0], len);/*内含对数据包的解析,比如判断是IP包或是ARP包,然后交由各自的函数进行处理*/
return len;
}
- 无标签