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的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数据收发函数同样参考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; } |