Internet作为全球最大的互联网络,几乎总汇了全球的信息资源。作为我们生活以及发展的基础设施,越来越多的设备需要连接Internet,共享信息资源。由于Internet的开放性,任何厂家基于TCP/IP协议、Internet技术生产的网络适配器均能接入Internet,与全球计算机互连通信。笔者此处就DM9000A网卡驱动实现作一个简单的介绍。

1. DM9000A概述

DM9000A为台湾DAVICOM公司推出的高速以太网接口芯片,该芯片完全集成的和符合成本效益单芯片快速以太网MAC控制器与一般处理接口,支持8位、16位、32位IO总线接口访问,完全符合IEEE 802.3u规范。

2. MAC帧

TCP/IP协议为Internet互联协议,采用4层层级结构:应用层、运输层、网络层、链路层。其中网卡工作在数据链路层,因此只能够发送或接收数据链路层的帧数据。大多数的TCP/IP均采用Ethernet V2帧格式,数据链路层的上一层的帧包,如网络层的数据包ARP包、RARP包、IP数据包(TCP包/UDP包)等,通过在包头加入6 字节MAC目的地址、6字节MAC源地址、2字节高层包类型,在包尾加入4字节帧检验序列即构成MAC帧。

MAC帧要求数据长度在64 byte ~ 1518 byte之间,这是由协议的防碰撞机制、信道利用率等决定的。因此链路层的上一层包帧长度要求在46 byte ~ 1500 byte,如果数据包长度小于46 byte,如ARP包、RARP包,则必须填充到46 byte,如果一次数据包大于1500byte,也必须进行分包传输。不然会被接收端网卡认为是错误的包,直接丢弃。

接收端网卡接收到CRC正确的MAC帧之后,根据MAC帧的MAC目的地址,比较自身MAC地址,如果一致,或者是组播地址、广播地址,则认为是传给自身的包,接收下来,并传给上一层进行处理,否则直接丢弃不属于自身的包。数据链路层并没有重传或者确定功能,对于传输过程出错的包,仅仅是丢弃,数据包的可靠传输、出错处理等需上面的层级处理。

09_网卡驱动实现 - 图1

2-1. MAC帧格式

3. DM9000A驱动

网卡驱动一般应实现最基本的三个接口,网卡初始化、网卡发送包、网卡接收包。

3.1. 网卡初始化

网卡初始化函数主要对DM9000A进行总线引脚的配置以及总线访问时序的设置,配置相应的中断处理,并初始化相应的DM9000A寄存器,最后使能相应的发送/接收回路。芯片总线访问时序应根据芯片手册设置最佳的参数,不然CPU直接访问总线将极大地降低CPU性能。中断处理为CPU提供了一种异步工作的方式,只有相应的事件到来,CPU才需处理相应的事件,能够加快系统响应以及提高系统效率,此处根据中断信号确定包发送完事件,包接收事件,网络连接/断开事件。最后,根据相应的手册或规范完成DM9000A寄存器的配置并使能收发回路。

int DM9000_Init(void)

{
    uint32_t Id;
    int32_t i;
    int32_t oft;
    // Memory Bank1, 16 bit
    SROM_BW_REG &= ~(0xf << 4);
    SROM_BW_REG |= (0<<7) | (0<<6) |(1<<5) | (1<<4);
    // DM9000A最大的连续读/写时间为4clk(20ns/clk),一次读/写周期设置在100ns, 13 clk@133M
    // Tacs 0, Tcos 1, Tacc 4, Tacp 6, Tcoh 1, Tcah 1
    SROM_BC1_REG = (4<<4) | (1<<8) |(1<<12) | (6<<16) | (1<<24) | (0<<28);
    MP01CON_REG = (MP01CON_REG & (~(0xf<<4))) |(2<<4); // SROM_CSn[1]
    // set pin interrupt
    GPH1CON_REG = GPH1CON_REG | (0xf<<4); // EINT_9
    //GPH1PUD_REG = (GPH1PUD_REG & (~(0x3<<2)))| (0x1<<2); // pull down
    EXT_INT_1_CON_REG = (EXT_INT_1_CON_REG &(~(0x7<<4))) | (3<<4); // rising edge
    IRQ_RegisterInt(INT_EINT9, EINT9_Handler);
    IRQ_EnableInt(INT_EINT9);
    EXT_INT_1_PEND_REG |= (1<<1); // clear pending
    EXT_INT_1_MASK_REG &= ~(1<<1); // enableEINT9
    // RESET device
    DM9000_WriteReg(DM9000_NCR, NCR_RST);
    Delay_us(100);
    DM9000_WriteReg(DM9000_NCR, NCR_RST);
    Delay_us(100);
    // Read id
    Id = DM9000_ReadReg(DM9000_VIDL);
    Id |= DM9000_ReadReg(DM9000_VIDH) << 8;
    Id |= DM9000_ReadReg(DM9000_PIDL) << 16;
    Id |= DM9000_ReadReg(DM9000_PIDH) << 24;
    if (Id == DM9000_ID)
        Debug("DM9000found at 0x%08x, id: 0x%08x\n", DM9000_IO, Id);
    else {
        Debug("DM9000not found at 0x%08x, id: 0x%08x\n", DM9000_IO, Id);
        return -1;
    }
    DM9000_WriteReg(DM9000_GPR, 0x00);  // Enable PHY
    Delay_us(1000);
    // Set PHY
    DM9000_WritePhy(0x04, 0x101); // Set PHY media mode
    DM9000_WritePhy(0x00, 0x3100);
    // Program operating register
    DM9000_WriteReg(DM9000_NCR, 0x0);   // only intern phy supported by now
    DM9000_WriteReg(DM9000_TCR, 0); // TX Polling clear
    DM9000_WriteReg(DM9000_BPTR, 0x3f); // Less 3Kb, 600us
    DM9000_WriteReg(DM9000_FCTR,FCTR_HWOT(3)|FCTR_LWOT(8));// Flow Control : High/Low Water
    DM9000_WriteReg(DM9000_FCR, 0x08);
    DM9000_WriteReg(DM9000_SMCR, 0);    // Special Mode
    DM9000_WriteReg(DM9000_NSR,NSR_WAKEST|NSR_TX2END|NSR_TX1END); // clear TX status
    DM9000_WriteReg(DM9000_ISR, 0x0F);  // Clear interrupt status
    // Set Node address
    oft = 0x10;
    for (i=0; i<6; i++) {
        DM9000_WriteReg(oft,default_enetaddr[i]);
        oft++;
    }
    oft = 0x16;
    for (i=0; i<6; i++) {
        DM9000_WriteReg(oft,0);
        oft++;
    }
    oft = 0x10;
    Debug("MAC: ");
    for (i=0; i<5; i++) {
        Debug("%02x:",DM9000_ReadReg(oft));
        oft++;
    }
    Debug("%02x\n", DM9000_ReadReg(oft));
    // Activate DM9000
    DM9000_WriteReg(DM9000_RCR, RCR_DIS_LONG|RCR_DIS_CRC|RCR_RXEN);//RX enable
    DM9000_WriteReg(DM9000_IMR, 0xa3);  // Enable TX/RX/Link interrupt
    return 0;
}

3.2. 网卡发送包

写入网卡发送缓存的数据包应该是MAC帧,一次写入不能大于MAC帧最大长度,由于初始化时使能了帧填充以及帧检测序列填充功能,在启动发送时,发送缓存数据如果小于64 byte,会自动进行填充到最小MAC帧长度包,并自动加入CRC发送出去。DM9000A写发送缓存需要相应的寄存器访问序列,应保证是原子操作,即写缓存过程中不能产生其它的DM9000A事件中断(如接收中断),不然在中断中访问了其它的DM9000A寄存器后再返回继续写缓存将不会成功。因此在写缓存前先禁止DM9000A中断,写完后再开启中断。

int DM9000_SendPacket(void *pBuffer, int32_t Len)
{
    uint16_t *pTemp;
    int32_t TempLen;
    int32_t i;
    int8_t TempStatus;
    if ((pBuffer == NULL) || (Len==0))
    {
        return -1;
    }
    // 禁止所有中断
    DM9000_WriteReg(DM9000_IMR, 0x80);
    // Move data to DM9000 TX RAM
    __REGb(DM9000_IO) = DM9000_MWCMD;
    pTemp = (uint16_t *)pBuffer;
    TempLen = (Len+1) / 2;
    for (i=0; i<TempLen; i++)
        __REGw(DM9000_DATA)= pTemp[i];
    // Set TX length to DM9000
    DM9000_WriteReg(DM9000_TXPLL, Len&0xff);
    DM9000_WriteReg(DM9000_TXPLH,(Len>>8)&0xff);
    // Issue TX request command
    DM9000_WriteReg(DM9000_TCR, TCR_TXREQ);
    // 重新使能中断
    DM9000_WriteReg(DM9000_IMR, 0xa3);
    DM9000_TxStatus = 0;
    while ((TempStatus = DM9000_TxStatus) != NET_TX_OK) {
        if(TempStatus != 0)
        {
            returnTempStatus;
        }
    }
    return 0;
}

3.3. 网卡接收包

在相应的中断处理中判断出相应的接收包事件后,即可启动读DM9000A接收缓存序列,将数据包从网卡读入到内存。同样,读数据包缓存也应保证原子操作。


int DM90000_ReceivePacket(void *pBuffer)

{
    int Status;
    int i;
    int RxLen;
    uint8_t RxReady;
    if (!DM9000_RxStatus) {
        return 0; // 没有包接收事件
    }
// 禁止所有中断
    DM9000_WriteReg(DM9000_IMR, 0x80);
    DM9000_RxStatus = 0;
// 接收到包
    RxReady = DM9000_ReadReg(DM9000_MRCMDX); // Dummy read
    RxReady = DM9000_ReadReg(DM9000_MRCMDX); // Got mostupdated data
// 0:not packet, 1:packet received, other:error
    if (RxReady != 0x01) {
        if (RxReady) {
            DM9000_WriteReg(DM9000_RCR,0x00); // Stop Device
            DM9000_WriteReg(DM9000_IMR,0x80); // mask int
            Debug("DM9000error: status check fail: 0x%x\n", RxReady);
            return-1;
        }
        DM9000_WriteReg(DM9000_IMR,0xa3);
        return 0;
    }
// A packet ready now & Get status/length
    __REGb(DM9000_IO) = DM9000_MRCMD;
    __REGw(DM9000_DATA); // status
    RxLen = __REGw(DM9000_DATA);
    if ((RxLen>DM9000_PKT_MAX) || (RxLen<0x40)) {
        Debug("DM9000error packet: RX Len 0x%x\n", RxLen);
        Status = -1;
    }
    else {
        Status =RxLen;
    }
// Read received packet from RX SRAM
    RxLen = (RxLen+1) / 2;
    for (i=0; i<RxLen; i++) {
        ((uint16_t*)pBuffer)[i] = __REGw(DM9000_DATA);
    }
    DM9000_WriteReg(DM9000_IMR, 0xa3);
    return Status;
}

3.4. 中断处理

DM9000A通过外部中断告知CPU相应的到来事件,应保证系统的实时,一般在中断中不应进行直接的事件处理,而是设置相应的事件标志,进而唤醒相应的事件进程进行处理。

static void EINT9_Handler(void)
{
    uint8_t IntStatus;
// 禁止所有中断
    DM9000_WriteReg(DM9000_IMR, 0x80);
    IntStatus = DM9000_ReadReg(DM9000_ISR);
    DM9000_WriteReg(DM9000_ISR, IntStatus);
    if (IntStatus & (1<<0)) {
        DM9000_RxStatus= NET_RX_OK;
    }
    if (IntStatus & (1<<1)) {
        // 发送完成
        DM9000_TxStatus= NET_TX_OK;
    }
    if (IntStatus & (1<<5)) {
        // 连接改变
        if(DM9000_ReadReg(DM9000_NSR) & (1<<6)) {
            Debug("Link OK\n");
            DM9000_LinkStatus = 1;
        }
        else {
            Debug("Link failed\n");
            DM9000_LinkStatus = 0;
        }
    }
    EXT_INT_1_PEND_REG |= (1<<1); // clear pending
// 重新使能中断
    DM9000_WriteReg(DM9000_IMR, 0xa3);
}

4. 驱动测试

此处应用以ARP包为例,ARP(地址解析协议)是根据IP地址来获取MAC地址的一种协议,上层应用是使用IP地址,但实现链路是使用MAC地址通信的。发送主机将包含目标IP地址的ARP请求广播到网络上的所有主机,目标IP地址的主机接收到该请求后,将返回ARP应答包给发送主机,应答包中即包含目标IP的MAC地址。

应用测试通过构造ARP包并发送出去,解析接收到的ARP应答包,并显示相应的目标MAC地址。

目标板可以通过路由器直接连接到局域网,也可以直连PC端网卡进行测试。连接PC端网卡时,应静态设置PC端IP地址以及目标板IP地址,应在同一局域网内。连接PC端网卡时并不用区分用交叉网线还是用直连网线,目前大部分的PC网卡都是自适应的,都是可以通信的。目标板连接路由器,应设置路由器端口所在的局域网IP。

void main(void)

{
    uint16_t PacketBuffer[512];
    ARP_PKT Arp_Packet;
    ARP_PKT *pArp;
    uint32_t IP_Addr;
    int Status;
    int Len;
    uint8_t Command;
    Uart_Init();
    DM9000_Init();
    memcpy(Arp_Packet.et_hdr.et_dst, NetBcastAddr, 6);
    memcpy(Arp_Packet.et_hdr.et_src, default_enetaddr, 6);
    Arp_Packet.et_hdr.et_type = htons(PROT_ARP);
// Ethernet hardware address
    Arp_Packet.arp_hdr.ar_hrd = htons(0x0001);
    Arp_Packet.arp_hdr.ar_pro = htons(PROT_IP);
    Arp_Packet.arp_hdr.ar_hln = 6;
    Arp_Packet.arp_hdr.ar_pln = 4;
    Arp_Packet.arp_hdr.ar_op = htons (ARPOP_REQUEST);
// source ET addr
    memcpy(Arp_Packet.arp_hdr.ar_sha, default_enetaddr,6);
// source ip addr
    IP4_ADDR(IP_Addr, IP_ADDR[0], IP_ADDR[1], IP_ADDR[2],IP_ADDR[3]);
    memcpy(Arp_Packet.arp_hdr.ar_spa, &IP_Addr, 4);
// dest ET addr = 0
    memset(Arp_Packet.arp_hdr.ar_tha, 0, 6);
    IP4_ADDR(IP_Addr, DST_ADDR[0], DST_ADDR[1],DST_ADDR[2], DST_ADDR[3]);
    memcpy(Arp_Packet.arp_hdr.ar_tpa, &IP_Addr, 4);
    printf("1. ARP Request test\n");
    while (1) {
        Len = DM90000_ReceivePacket(PacketBuffer);
        if (Len > 0) {
            printf("Rxlen %d\n", Len);
            pArp =(ARP_PKT *)PacketBuffer;
            if(ntohs(pArp->et_hdr.et_type) == PROT_ARP) {
                if(ntohs(pArp->arp_hdr.ar_op) == ARPOP_REPLY) {
                    // ARP 应答包
                    printf("DstIP: %d.%d.%d.%d, Dst MAC: %2X:%2X:%2X:%2X:%2X:%2X\n",
                           DST_ADDR[0],DST_ADDR[1], DST_ADDR[2], DST_ADDR[3],
                           pArp->arp_hdr.ar_sha[0],pArp->arp_hdr.ar_sha[1], pArp->arp_hdr.ar_sha[2],
                           pArp->arp_hdr.ar_sha[3],pArp->arp_hdr.ar_sha[4], pArp->arp_hdr.ar_sha[5] );
                    printf("1. ARP Requesttest\n");
                }
            }
        }
        Command = Uart_TestChar();
        if (!Command) {
            Command =Uart_WaitChar();
            Status =DM9000_GetLinkStatus();
            if (Status) {
                if (Command== '1') {
                    printf("SourceIP: %d.%d.%d.%d, Source MAC: %2X:%2X:%2X:%2X:%2X:%2X\n",
                           IP_ADDR[0],IP_ADDR[1], IP_ADDR[2], IP_ADDR[3],
                           default_enetaddr[0],default_enetaddr[1], default_enetaddr[2],
                           default_enetaddr[3],default_enetaddr[4], default_enetaddr[5] );
                    DM9000_SendPacket(&Arp_Packet,sizeof(ARP_PKT));
                }
            }
            else {
                printf("Waitingfor net connection\n");
            }
        }
    }
}

5. 附录

OK210_IAR_DM9000.rar,包含DM9000A驱动模块实现及ARP测试应用工程。
源码:http://pan.baidu.com/s/1ntpH3zN