网卡概述

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;
}

  • 无标签