嵌入式Linux——网卡驱动(3):结合硬件分析厂家提供驱动代码dm9dev9000c.c

2019-07-12 16:40发布

声明:我看完韦东山老师所讲解的关于DM9000C移植的视频后,感觉老师没有对视频中所用的芯片进行详细的讲解,同时也没有对厂家所提供的代码做进一步的分析。可能有些同学会和我一样开始对所学的知识有了一种朦胧感,同时又想通过学习让自己对这方面的知识有更深入的了解,并且在代码中对硬件的操作有所了解。如果是这样,我想你可以看看这篇文章,他可能让你对芯片,或者对代码有更好的了解。还是那句老话,如果我的文章对你有所帮助,那是我的荣幸,谢谢。 同时感谢南京电研电力自动化股份有限公司为我们翻译了DM9000A的数据手册。因为这两个芯片太相似了,所以我很多时候直接使用这个翻译好的DM9000A的数据手册(如果你需要可以给我留言)。 下面我们言归正传,首先介绍我们所用到的芯片:DM9000C。     DM9000是一款高度集成低功耗快速以太网处理器,该芯片集成了MAC和PHY。DM9000可以和CPU直接连接,支持8位、16位和32位数据总线宽度。该芯片支持10M和100M自适应以太网接口,内部有16K的FIFO以及4K双字节SRAM,支持全双工工作  DM9000内部还集成了接收缓冲区,可以在接收到数据的时候把数据存放到缓冲区中,链路层可以直接把数据从缓冲区取走。 我想我们会在很多文章上看到对于DM9000这样的描述。我也不例外。而上面这些都不是我主要想介绍的,而我想介绍的是DM9000C的寄存器列表,因为我们在后面对程序介绍的时候就要用到对这些寄存器的操作。下面我将寄存器列表复制下来,而寄存器具体的功能我会在程序中介绍。   好了,前面讲了这么多,下面我们开始分析程序,我们同样先从入口函数分析: int __init init_module(void) { switch(mode) { case DM9KS_10MHD: case DM9KS_100MHD: case DM9KS_10MFD: case DM9KS_100MFD: media_mode = mode; break; default: media_mode = DM9KS_AUTO; } dmfe_dev = dmfe_probe(); if(IS_ERR(dmfe_dev)) return PTR_ERR(dmfe_dev); return 0; } 上面函数中有两个需要注意的,首先就是DM9000C的模式选择,我们知道DM9000有两种模式,即同时支持10Mbps和100Mbps两种速率。同时我们的芯片又有一个很强大的功能就是自协商,即通信速率通过双方协商,协商的结果是两个设备中能同时支持的最大速度和最好的双工模式。这个技术被称为Auto Negotiation或者NWAY 图中用红线框起来的就是芯片中所对应的自协商功能了。而在上面程序中我们使用默认的自协商模式。 而在上面程序中,我们另一个需要注意的就是probe函数了。我想学驱动的人对probe函数都有特殊的感情,因为从这里开始,我们就是要真正开始探索DM9000,为它分配地址,然后注册他ps:我会将一些没有必要的判断语句删掉,使程序脉络更清晰)。 struct net_device * __init dmfe_probe(void) { struct net_device *dev; int err; DMFE_DBUG(0, "dmfe_probe()",0); dev= alloc_etherdev(sizeof(struct board_info)); /* 分配一个net_device结构体 */ if(!dev) return ERR_PTR(-ENOMEM); SET_MODULE_OWNER(dev); err = dmfe_probe1(dev); /* 设置net_device结构体 */ if (err) goto out; err = register_netdev(dev); /* 注册这个net_device结构体 */ if (err) goto out1; return dev; out1: release_region(dev->base_addr,2); out: free_netdev(dev); return ERR_PTR(err); } 我在自己的上一篇文章:

嵌入式Linux——网卡驱动(1):网卡驱动框架介绍

  中讲到,网络驱动程式的步骤为:    1.分配一个net_device结构体:alloc_etherdev();
 2.设置net_device结构体
 3.注册net_device结构体:register_netdev()。
而在上面的程序中步骤1和步骤3都已经有了,所以我们现在详细分析步骤2:设置net_device结构体,也就是dmfe_probe1(dev)函数,而在该函数中我们就要用到DM9000硬件方面的知识了。 int __init dmfe_probe1(struct net_device *dev) { struct board_info *db; /* Point a board information structure */ u32 id_val; u16 i, dm9000_found = FALSE; u8 MAC_addr[6]={0x00,0x60,0x6E,0x33,0x44,0x55}; u8 HasEEPROM=0,chip_info; DMFE_DBUG(0, "dmfe_probe1()",0); /* Search All DM9000 serial NIC */ do { outb(DM9KS_VID_L, iobase); id_val = inb(iobase + 4);     /* 读厂家ID的低字节 */ outb(DM9KS_VID_H, iobase); id_val |= inb(iobase + 4) << 8;      /* 读厂家ID的高字节 */ outb(DM9KS_PID_L, iobase); id_val |= inb(iobase + 4) << 16; /* 读设备ID的低字节 */ outb(DM9KS_PID_H, iobase); id_val |= inb(iobase + 4) << 24;      /* 读设备ID的高字节 */ if (id_val == DM9KS_ID || id_val == DM9010_ID) { /* 将读到的与芯片的比较,如果一样则继续运行 */ /* Request IO from system */ if(!request_region(iobase, 2, dev->name)) /* 分配网络设配器所占内存 */ return -ENODEV; printk(KERN_ERR" I/O: %x, VID: %x ",iobase, id_val); dm9000_found = TRUE; /* Allocated board information structure */ memset(dev->priv, 0, sizeof(struct board_info)); /* 设置net_device结构体的私有数据大小为board_info大小 */ db = (board_info_t *)dev->priv; /* 将board_info结构体放到dev的私有数据中 */ dmfe_dev = dev; db->io_addr = iobase; /* 设置芯片命令寄存器的基地址 */ db->io_data = iobase + 4; /* 设置芯片数据寄存器的基地址,而此处为什么加4,是因为CMD引脚与地址laddr2引脚相连 */ db->chip_revision = ior(db, DM9KS_CHIPR); /* 获取芯片的版本信息,CHIPR为芯片版本寄存器地址为2CH */ chip_info = ior(db,0x43); /* 而这句话我在芯片手册上没有找到相应的寄存器,所以不知道读什么信息 */ if((db->chip_revision!=0x1A) || ((chip_info&(1<<5))!=0) || ((chip_info&(1<<2))!=1)) return -ENODEV; /* driver system function,下面就是驱动系统函数的设置了 */ dev->base_addr = iobase; /* 设置IO基地址 */ dev->irq = irq; /* 设置中断序列号,在本程序中使用外部中断7,这在后面设置 */ dev->open = &dmfe_open; /* 设置open函数,当打开网卡时调用该函数 */ dev->hard_start_xmit = &dmfe_start_xmit; /* 设置传输函数,当要传输数据时,调用该函数 */ dev->watchdog_timeo = 5*HZ;               /* 设置超时时间 */ dev->tx_timeout = dmfe_timeout; /* 设置超时函数 */ dev->stop = &dmfe_stop; /* 设置停止网卡函数 */ dev->get_stats = &dmfe_get_stats; /* 设置获得传输状态函数 */ dev->set_multicast_list = &dm9000_hash_table; dev->do_ioctl = &dmfe_do_ioctl; #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,28) dev->ethtool_ops = &dmfe_ethtool_ops; #endif #ifdef CHECKSUM dev->features |= NETIF_F_IP_CSUM|NETIF_F_SG; #endif /* 设置MII总线 */ db->mii.dev = dev; /* 设置MII接口 */ db->mii.mdio_read = mdio_read; /* MII方式读函数 */ db->mii.mdio_write = mdio_write; /* MII方式写函数 */ db->mii.phy_id = 1; db->mii.phy_id_mask = 0x1F; db->mii.reg_num_mask = 0x1F; /* Read SROM content,如果芯片接有EEPROM,读EEPROM的内容 */ for (i=0; i<64; i++) ((u16 *)db->srom)[i] = read_srom_word(db, i); /* Get the PID and VID from EEPROM to check,从EEPROM中获得PID和VID进行比较 */ id_val = (((u16 *)db->srom)[4])|(((u16 *)db->srom)[5]<<16); printk("id_val=%x ", id_val); if (id_val == DM9KS_ID || id_val == DM9010_ID) HasEEPROM =1; /* Set Node Address,设置节点地址 */ for (i=0; i<6; i++) { if (HasEEPROM) /* use EEPROM,如果有EEPROM使用EEPROM中的值 */ dev->dev_addr[i] = db->srom[i]; else /* No EEPROM ,如果没有,使用厂家提供的MAC值*/ dev->dev_addr[i] = MAC_addr[i]; } }//end of if() iobase += 0x10; }while(!dm9000_found && iobase <= DM9KS_MAX_IO); return dm9000_found ? 0:-ENODEV; } 上面就是设置net_device结构体以及做DM9000硬件相关的设置,下面我们将他们细分一下,可以分为: 1.读取芯片的厂家ID和设备ID,并与厂家提供的进行比较 2.设置board_info结构体 3.设置net_device结构体 4.设置MII总线 5.看DM9000芯片是否外接有EEPROM芯片,并做相应的设置 好了,我们下面就对这几方面中比较重要的做更加详细的分析,而在分析之前,我想有些人会对board_info结构体有些疑惑,因为我们在前面的文章中已经对net_device结构体进行了分析却没有介绍board_info结构体,下面我们先对board_info结构体进行介绍: /* Structure/enum declaration ------------------------------- */ typedef struct board_info { u32 io_addr;    /* Register I/O base address,命令寄存器IO基地址 */ u32 io_data;    /* Data I/O address ,数据寄存器IO地址*/ u8 op_mode;     /* PHY operation mode ,PHY操作模式*/ u8 io_mode;     /* 0:word, 2:byte ,IO模式,0位字,而2为字节*/ u8 Speed; /* current speed,当前的速度 */ u8 chip_revision; int rx_csum;    /* 0:disable, 1:enable,接收计数使能,0为不使能,1为使能 */ u32 reset_counter;/* counter: RESET */ u32 reset_tx_timeout;/* RESET caused by TX Timeout */ int tx_pkt_cnt; /* 传输包计数 */ int cont_rx_pkt_cnt;/* current number of continuos rx packets , */ struct net_device_stats stats; /* 设备传输统计 */ struct timer_list timer; /* 时间列表 */ unsigned char srom[128]; spinlock_t lock; /* 自旋锁 */ struct mii_if_info mii; /* MII总线信息 */ } board_info_t; 通过上面的介绍我们对board_info结构体有了一定的了解,同时也知道他所包含的内容,而下面我们就要讲解本驱动的重点,也就是上面的设置net_device结构体。而设置net_device最主要就是设置他的open函数,发送函数和接收函数。我们知道当我们要打开DM9000时就要使用到open函数,所以我们先讲解open函数。而在open函数中,厂家也做了不少DM9000的初始化。下面我们详细分析: static int dmfe_open(struct net_device *dev) { board_info_t *db = (board_info_t *)dev->priv; u8 reg_nsr; int i; DMFE_DBUG(0, "dmfe_open", 0); if (request_irq(dev->irq,&dmfe_interrupt,0,dev->name,dev)) /* 注册中断函数 */ return -EAGAIN; /* Initilize DM910X board,初始化DM9000 */ dmfe_init_dm9000(dev); /* Init driver variable,初始化驱动变量 */ db->reset_counter = 0; db->reset_tx_timeout = 0; db->cont_rx_pkt_cnt = 0; /* check link state and media speed,检测连接状态和设备速度 */ db->Speed =10; i=0; do { reg_nsr = ior(db,DM9KS_NSR); if(reg_nsr & 0x40) /* link OK!! */ { /* wait for detected Speed */ mdelay(200); reg_nsr = ior(db,DM9KS_NSR); if(reg_nsr & 0x80) db->Speed =10; else db->Speed =100; break; } i++; mdelay(1); }while(i<3000); /* wait 3 second */ /* set and active a timer process,设置并使能时间处理函数 */ init_timer(&db->timer); db->timer.expires = DMFE_TIMER_WUT; db->timer.data = (unsigned long)dev; db->timer.function = &dmfe_timer; add_timer(&db->timer); //Move to DM9000 initiallization was finished. netif_start_queue(dev); return 0; } 从上面我们可以看出他做了三方面的工作: 1.申请中断 2.初始化DM9000 3.设置board_info结构体 下面我们一一分析,先从第一个申请中断request_irq(dev->irq,&dmfe_interrupt,0,dev->name,dev) 我们可以看到,在第一个参数中使用的是dev->irq,而我们前面说了,这是要我们根据自己的开发板设置的,而此处我们用的是外部中断7,第二个参数是中断处理函数,我们会在后面接收数据时说到。而第三个参数是中断触发方式,在这里的参数是0,即没有边沿是不合理的,而此处我们将会改为上升沿触发。 下面我们来分析初始化DM9000函数:dmfe_init_dm9000(dev),这其中就要用到对DM9000的寄存器的设置了。 static void dmfe_init_dm9000(struct net_device *dev) { board_info_t *db = (board_info_t *)dev->priv; DMFE_DBUG(0, "dmfe_init_dm9000()", 0); spin_lock_init(&db->lock); /* 初始化自旋锁 */ iow(db, DM9KS_GPR, 0); /* 设置通用目的寄存器(寄存器号为1Fh),设置PHY启动 */ mdelay(20); /* 等待PHY启动就绪 */ /* 软件重置然后在等20us */ iow(db, DM9KS_NCR, 3); /* 设置网络设置寄存器,使用软件重置 */ udelay(20); /* 为软件重置成功,等待20us */ iow(db, DM9KS_NCR, 3); /* 设置网络设置寄存器NCR (reg_00h)的bit[0] 软件复位并且设置回环模式为MAC内部回环 */ udelay(20); /* 为软件重置成功,等待20us */ /* I/O mode */ db->io_mode = ior(db, DM9KS_ISR) >> 6; /* 中断状态寄存器ISR的bit7:6 保存着I/O mode,0为16bit而1为8bit */ /* Set PHY */ db->op_mode = media_mode; /* 设置芯片的模型,我们在开始的时候已经将其设置为自协商模式 */ set_PHY_mode(db); /* 操作寄存器设置DM9000 */ iow(db, DM9KS_NCR, 0); iow(db, DM9KS_TCR, 0); /* TX Polling clear */ iow(db, DM9KS_BPTR, 0x3f); /* Less 3kb, 600us */ iow(db, DM9KS_SMCR, 0); /* Special Mode */ iow(db, DM9KS_NSR, 0x2c); /* clear TX status */ iow(db, DM9KS_ISR, 0x0f); /* Clear interrupt status */ iow(db, DM9KS_TCR2, 0x80); /* Set LED mode 1 */ if (db->chip_revision == 0x1A){ /* Data bus current driving/sinking capability */ iow(db, DM9KS_BUSCR, 0x01); /* default: 2mA */ } #ifdef FLOW_CONTROL iow(db, DM9KS_BPTR, 0x37); iow(db, DM9KS_FCTR, 0x38); iow(db, DM9KS_FCR, 0x29); #endif if (dev->features & NETIF_F_HW_CSUM){ printk(KERN_INFO "DM9KS:enable TX checksum "); iow(db, DM9KS_TCCR, 0x07); /* TX UDP/TCP/IP checksum enable */ } if (db->rx_csum){ printk(KERN_INFO "DM9KS:enable RX checksum "); iow(db, DM9KS_RCSR, 0x02); /* RX checksum enable */ } /* Set address filter table */ dm9000_hash_table(dev); /* Activate DM9000/DM9010 */ iow(db, DM9KS_IMR, DM9KS_REGFF); /* Enable TX/RX interrupt mask */ iow(db, DM9KS_RXCR, DM9KS_REG05 | 1); /* RX enable */ /* Init Driver variable */ db->tx_pkt_cnt = 0; netif_carrier_on(dev); } 上面主要做的工作有: 1.初始化自旋锁 2.启动PHY 3.软件重置 4.设置IO模式(即16bit还是8bit)和PHY 5.操作寄存器,设置DM9000 而前面的几项工作代码介绍的已经很详细了,而最后一项是需要我们结合DM9000的数据手册来进行设置的,这里我将主要的写出来: /* Program operating register, only internal phy supported */
    dm9000_reg_write(DM9000_NCR, 0x0); //支持内部PHY(网络控制寄存器)
    /* TX Polling clear */
    dm9000_reg_write(DM9000_TCR, 0);// 发送控制寄存器,
        /* Less 3Kb, 200us *//*背压阈值寄存器,背压阈值最高值为默认值3k,拥挤状态时间为600US */
    dm9000_reg_write(DM9000_BPTR, BPTR_BPHW(3) | BPTR_JPT_600US);
        /* Flow Control : High/Low Water,流控制阈值寄存器,RX FIFO缓存高位溢出门限使用默认值3k,而RX FIFO缓 存低位溢出门限也是用默认值8k*/
    dm9000_reg_write(DM9000_FCTR, FCTR_HWOT(3) | FCTR_LWOT(8));
        /* SH FIXME: This looks strange! Flow Control,接收/发送流控制寄存器,禁止流控制模式*/
    dm9000_reg_write(DM9000_FCR, 0x0);
    /* Special Mode,特殊模式控制寄存器*/
    dm9000_reg_write(DM9000_SMCR, 0);
        /* clear TX status,网络状态寄存器,清除唤醒事件状态,清除TX数据包2完成标志,清除TX数据包1完成标志*/
    dm9000_reg_write(DM9000_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
        /* Clear interrupt status,中断状态寄存器,清除接受溢出计数器溢出,清除接收溢出,清除数据包发送,清除数据包接收*/
        dm9000_reg_write(DM9000_ISR, ISR_ROOS | ISR_ROS | ISR_PTS | ISR_PRS); 激活DM9000         dm9000_reg_write(DM9000_RCR, RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN);// 接收控制寄存器, 丢弃数据包长度超过1522字节的数据包,丢弃CRC校验错误数据包,接收使能    Enable TX/RX interrupt mask,使能发送接收屏蔽中断         dm9000_reg_write(DM9000_IMR, IMR_PAR);// 中断屏蔽寄存器, 使能数据包接收屏蔽中断, 使能数据包发送屏蔽中断  上面的初始化DM9000中我只写了大概,而具体的针对寄存器的操作各位读者要自己去看数据手册,然后根据手册的内容与程序进行对照。而设置board_info结构体其实就是将board_info结构体中的各个参数的真实值填入到其相应的参数中。这里就不在细说。 而讲完open函数,我们就应该讲下一个十分重要的数据发送函数:dev->hard_start_xmit = &dmfe_start_xmit;而在介绍发送函数之前还有一个基础知识要介绍给大家,那就是DM9000中的SRAM(这个在后面程序中用)。 如上图所示,在DM9000内部SRAM中,地址0x0000~0x0BFF是TX Buffer, 地址0x0C00~0x3FFF是RX Buffer。在发送一个包之前,包中的有效数据必须先被存储到TX Buffer中并且使用输出端口命令来选择MWCMD寄存器。包的长度定义在TXPLL和TXPLH中。最后设置TXCR寄存器的bit[0] TXREQ来自动发送包。如果设置了IMR寄存器的PTM位,则DM9000会产生一个中断触发在ISR寄存器的bit[1]=PTS=1, 同时设置一个完成标志在NSR寄存器的bit[2]=TX1END或者 bit[3]=TX2END,表示包已经发送完了。 有了上面的知识,下面我们开始介绍发送函数static int dmfe_start_xmit(struct sk_buff *skb, struct net_device *dev) { board_info_t *db = (board_info_t *)dev->priv; char * data_ptr; int i, tmplen; u16 MDWAH, MDWAL; DMFE_DBUG(0, "dmfe_start_xmit", 0); if (db->chip_revision != 0x1A) { if(db->Speed == 10) {if (db->tx_pkt_cnt >= 1) return 1;} else {if (db->tx_pkt_cnt >= 2) return 1;} }else if (db->tx_pkt_cnt >= 2) return 1; /* packet counting,发送数据包加一 */ db->tx_pkt_cnt++; db->stats.tx_packets++; /* 发送数据包加1 */ db->stats.tx_bytes+=skb->len; /* 发送数据长度加数据的长度 */ if (db->chip_revision != 0x1A) { if (db->Speed == 10) {if (db->tx_pkt_cnt >= 1) netif_stop_queue(dev);} /* 停止接收队列 */ else {if (db->tx_pkt_cnt >= 2) netif_stop_queue(dev);} }else if (db->tx_pkt_cnt >= 2) netif_stop_queue(dev); /* Disable all interrupt */ iow(db, DM9KS_IMR, DM9KS_DISINTR); /* 关闭所有中断 */ MDWAH = ior(db,DM9KS_MDWAH); /* 读内存数据写地址寄存器高字节 */ MDWAL = ior(db,DM9KS_MDWAL); /* 读内存数据写地址寄存器低字节 */ /* Set TX length to reg. 0xfc & 0xfd */ iow(db, DM9KS_TXPLL, (skb->len & 0xff)); /* 将数据包长度低字节写入传输数据包长度低字节寄存器 */ iow(db, DM9KS_TXPLH, (skb->len >> 8) & 0xff); /* 将数据包长度高字节写入传输数据包长度高字节寄存器 */ /* Move data to TX SRAM */ data_ptr = (char *)skb->data; /* 将sk_buff中数据的地址赋值给SRAM */ outb(DM9KS_MWCMD, db->io_addr); /* 操作内存数据写命令寄存器,向发送SRAM中写数据 */ switch(db->io_mode) /* 选择IO模式,为16bit或者8bit */ { case DM9KS_BYTE_MODE: for (i = 0; i < skb->len; i++) outb((data_ptr[i] & 0xff), db->io_data); break; case DM9KS_WORD_MODE: tmplen = (skb->len + 1) / 2; /* 计算发送长度 */ for (i = 0; i < tmplen; i++) /* 向SRAM中写入数据 */ outw(((u16 *)data_ptr)[i], db->io_data); break; case DM9KS_DWORD_MODE: tmplen = (skb->len + 3) / 4; for (i = 0; i< tmplen; i++) outl(((u32 *)data_ptr)[i], db->io_data); break; } /* Saved the time stamp,保存时间戳 */ dev->trans_start = jiffies; /* 写入发送数据包的时间戳 */ db->cont_rx_pkt_cnt =0; /* Free this SKB,释放sk_buff */ dev_kfree_skb(skb); /* Re-enable interrupt ,重新开启全部中断*/ iow(db, DM9KS_IMR, DM9KS_REGFF); return 0; } 而通过上面分析,我们可以总结发送数据包所要的步骤: 0. 程序进行发送流程时,首先通过 tx_pkt_cnt变量判断是否发送第一个数据包,DM9000的驱动设计第一个数据包可以被发送,第二个数据包通过 dm9000_tx_done()函数发送。如果发送的是第一数据包,则程序把发送数据包个数加1,通过设置 DM9000控制寄存器,通知发送数据包长度,然后向 DM9000写入发送命令 1.统计接收数据增加,包括统计接收数据包数和统计数据长度 2.停止接收队列,该队列是内核与网卡驱动之间的数据包队列,内核把发送的数据包放到队列中,网卡驱动从队列中取出数据包进行发送。 3.停止全部中断,目的是防止在发送数据包的过程中被打断,因为内核的代码都是可重入的,这点需要注意。 4.读内存数据写地址寄存器 5.数据包长度写入传输数据包长度寄存器 6.将sk_buff中数据的地址赋值给SRAM 7.操作内存数据写命令寄存器,向发送SRAM中写数据 8.计算发送长度  9.向SRAM中写入数据  10.保存时间戳 11.释放sk_buff 12.重新开启全部中断 上面就是要发送数据包的全部步骤了。分析了发送数据包函数,我们就应该分析接收数据包函数了。我们知道数据包什么时候来这是不确定的,所以我们有两种方法可以确定数据包什么时候来,一种是用查询的方式,即每隔一段时间查询一次是否有数据包到来。而另一种方式是中断的方式,即当有数据包来时,会触发中断处理函数,并作出反应。显然使用中断的方式是更加经济合理的方式。而我们的驱动程序也是使用的中断方式。当有中断到来时,我们会进入中断触发函数。我们会在中断触发函数中对接收包进行处理。下面我们先看中断触发函数,由于我们是在open函数中请求的中断,所以我们可以从那里找到中断处理函数:dmfe_interrupt if (request_irq(dev->irq,&dmfe_interrupt,0,dev->name,dev)) 中断处理函数为: static void dmfe_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct net_device *dev = dev_id; board_info_t *db; int int_status,i; u8 reg_save; DMFE_DBUG(0, "dmfe_interrupt()", 0); /* A real interrupt coming */ db = (board_info_t *)dev->priv; spin_lock(&db->lock); /* 对临界资源加锁 */ /* Save previous register address */ reg_save = inb(db->io_addr); /* 保存寄存器基地址 */ /* Disable all interrupt */ iow(db, DM9KS_IMR, DM9KS_DISINTR); /* 关闭所有中断 */ /* Got DM9000/DM9010 interrupt status */ int_status = ior(db, DM9KS_ISR); /* 读中断状态寄存器,获得中断状态*/ iow(db, DM9KS_ISR, int_status); /* 向中断状态寄存器获得中断位置写1,清除该中断 */ /* Link status change */ if (int_status & DM9KS_LINK_INTR) /* 判断连接状态*/ { netif_stop_queue(dev); /* 停止队列 */ for(i=0; i<500; i++) /*wait link OK, waiting time =0.5s */ { phy_read(db,0x1); if(phy_read(db,0x1) & 0x4) /*Link OK*/ { /* wait for detected Speed */ for(i=0; i<200;i++) udelay(1000); /* set media speed */ if(phy_read(db,0)&0x2000) db->Speed =100; else db->Speed =10; break; } udelay(1000); } netif_wake_queue(dev); /* 唤醒队列 */ //printk("[INTR]i=%d speed=%d ",i, (int)(db->Speed)); } /* 接收数据包 */ if (int_status & DM9KS_RX_INTR) dmfe_packet_receive(dev); /* 接收数据包函数 */ /* Trnasmit Interrupt check */ if (int_status & DM9KS_TX_INTR) /* 判断传输是否完成 */ dmfe_tx_done(0); if (db->cont_rx_pkt_cnt>=CONT_RX_PKT_CNT) { iow(db, DM9KS_IMR, 0xa2); } else { /* Re-enable interrupt mask */ iow(db, DM9KS_IMR, DM9KS_REGFF); /* 使能全部中断 */ } /* Restore previous register address */ outb(reg_save, db->io_addr); /* 恢复中断处理前中断寄存器地址 */ spin_unlock(&db->lock); /* 对临界资源解锁 */ #if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0) return IRQ_HANDLED; #endif } 上面就是中断处理函数,我们可以看到他调用接收数据包函数对数据进行处理,而具体是怎么处理数据的那?我们还要进入到这个函数中对其进行分析。而再看具体代码之前还是来看看DM9000的数据接收的过程。       接收的数据存储在RX SRAM中,地址是0C00h~3FFFh。存储在RX_SRAM中的每个包都有4个字节的信息头。可以使用MRCMDX和MRCMD寄存器来得到这些信 息。第一个字节用来检查数据包是否接收到了RX_SRAM中,如果这个字节是"01",意味着一个包已经接收。如果是"00",则还没有数据包被接收到 RX_SRAM中。第二个字节保存接收到的数据包的信息,格式和RSR寄存器一样。根据这个格式,接收到的包能被校验是正确的还是错误的包。第三和第四字 节保存了接收的数据包的长度。这四个字节以外的其他字节就是接收包的数据。看下图可以更好的理解这种格式。 下面是具体的代码: static void dmfe_packet_receive(struct net_device *dev) { board_info_t *db = (board_info_t *)dev->priv; struct sk_buff *skb; u8 rxbyte; u16 i, GoodPacket, tmplen = 0, MDRAH, MDRAL; u32 tmpdata; rx_t rx; u16 * ptr = (u16*)℞ u8* rdptr; DMFE_DBUG(0, "dmfe_packet_receive()", 0); db->cont_rx_pkt_cnt=0; do { /*保存内存数据读地址寄存器的值*/ MDRAH=ior(db, DM9KS_MDRAH); /* 读内存数据寄存器高字节数据 */ MDRAL=ior(db, DM9KS_MDRAL); /* 读内存数据寄存器低字节数据 */ /* 从接收SRAM中读数据,读取该指令之后,指向内部SRAM的读指针不变。DM9000A开始预取SRAM中数据到内部数据缓冲中 */ ior(db, DM9KS_MRCMDX); /* 读内存数据预取读命令寄存器 */ rxbyte = inb(db->io_data); /* 获取最新的更新信息 */ #ifdef CHECKSUM if (rxbyte&0x2) /* 检查接收字节 */ { printk("dm9ks: abnormal! "); dmfe_reset(dev); break; }else { if (!(rxbyte&0x1)) break; } #else if (rxbyte==0) break; if (rxbyte>1) { printk("dm9ks: Rxbyte error! "); dmfe_reset(dev); break; } #endif /* 数据包准备好,准备接收数据长度和状态 */ GoodPacket = TRUE; outb(DM9KS_MRCMD, db->io_addr); /* 向寄存器发送读命令 */ /* Read packet status & length */ switch (db->io_mode) /* 选择IO模式有8bit,16bit和32bit */ { case DM9KS_BYTE_MODE: *ptr = inb(db->io_data) + (inb(db->io_data) << 8); *(ptr+1) = inb(db->io_data) + (inb(db->io_data) << 8); break; case DM9KS_WORD_MODE: *ptr = inw(db->io_data); *(ptr+1) = inw(db->io_data); /* 将数据地址传给SRAM指针 */ break; case DM9KS_DWORD_MODE: tmpdata = inl(db->io_data); *ptr = tmpdata; *(ptr+1) = tmpdata >> 16; break; default: break; } /* Packet status check,包状态检测 */ if (rx.desc.status & 0xbf) /* 检查接收状态是否出错 */ { GoodPacket = FALSE; if (rx.desc.status & 0x01) { db->stats.rx_fifo_errors++; printk(KERN_INFO" "); /* 检查FIFO是否出错 */ } if (rx.desc.status & 0x02) { db->stats.rx_crc_errors++; printk(KERN_INFO" ");/* 检查CRC是否出错 */ } if (rx.desc.status & 0x80) /* 检查包长度是否出错 */ { db->stats.rx_length_errors++; printk(KERN_INFO" "); } if (rx.desc.status & 0x08)/* 检查物理层是否出错 */ printk(KERN_INFO" "); } if (!GoodPacket) /* 如果不是正确的包,就丢掉从新接收 */ { // drop this packet!!! switch (db->io_mode) { case DM9KS_BYTE_MODE: for (i=0; iio_data); break; case DM9KS_WORD_MODE: tmplen = (rx.desc.length + 1) / 2; for (i = 0; i < tmplen; i++) inw(db->io_data); break; case DM9KS_DWORD_MODE: tmplen = (rx.desc.length + 3) / 4; for (i = 0; i < tmplen; i++) inl(db->io_data); break; } continue;/*next the packet*/ } skb = dev_alloc_skb(rx.desc.length+4); /* 分配sk_buff */ if (skb == NULL ) { printk(KERN_INFO "%s: Memory squeeze. ", dev->name); /*re-load the value into Memory data read address register*/ iow(db,DM9KS_MDRAH,MDRAH); iow(db,DM9KS_MDRAL,MDRAL); return; } else { /* Move data from DM9000 */ skb->dev = dev; skb_reserve(skb, 2); rdptr = (u8*)skb_put(skb, rx.desc.length - 4); /* Read received packet from RX SARM,从接收SRAM中读取接收的包数据 */ switch (db->io_mode) { case DM9KS_BYTE_MODE: for (i=0; iio_data); break; case DM9KS_WORD_MODE: tmplen = (rx.desc.length + 1) / 2; for (i = 0; i < tmplen; i++) ((u16 *)rdptr)[i] = inw(db->io_data); /* 读取包数据 */ break; case DM9KS_DWORD_MODE: tmplen = (rx.desc.length + 3) / 4; for (i = 0; i < tmplen; i++) ((u32 *)rdptr)[i] = inl(db->io_data); break; } /* Pass to upper layer */ skb->protocol = eth_type_trans(skb,dev); /* 通知上层协议栈处理 */ #ifdef CHECKSUM if((rxbyte&0xe0)==0) /* receive packet no checksum fail */ skb->ip_summed = CHECKSUM_UNNECESSARY; #endif netif_rx(skb); /* 发送sk_buff */ dev->last_rx=jiffies; /* 设置时间戳 */ db->stats.rx_packets++; /* 更新统计数据包个数 */ db->stats.rx_bytes += rx.desc.length;/* 更新统计数据包长度 */ db->cont_rx_pkt_cnt++; if (db->cont_rx_pkt_cnt>=CONT_RX_PKT_CNT) { dmfe_tx_done(0); break; } } }while((rxbyte & 0x01) == DM9KS_PKT_RDY); DMFE_DBUG(0, "[END]dmfe_packet_receive()", 0); } 函数内部是一个大的 do……while{}循环。而在这个大的循环内,我们主要做的工作有: 1.保存内存数据读地址寄存器的值
2.读内存数据预取读命令寄存器 
3.获取最新的更新信息 4.向寄存器发送读命令
5.将数据地址传给SRAM指针
6.包状态检测
7.如果不是正确的包,就丢掉从新接收
8.如果是正确的包 分配sk_buff
9.从接收SRAM中读取接收的包数据 
10.通知上层协议栈处理
11.发送sk_buff 
12.设置时间戳
13.更新统计信息
现在我们发送函数就讲完了,我想讲完这个后大家可能有个疑问:在中断函数中我们有停止队列函数netif_stop_queue(dev);却没有唤醒队列函数,那唤醒队列函数去哪里了?其实唤醒队列函数就在中断函数中,只是他隐藏在dmfe_tx_done(0)中。这个函数就是数据传输完处理函数。 static void dmfe_tx_done(unsigned long unused) { struct net_device *dev = dmfe_dev; board_info_t *db = (board_info_t *)dev->priv; int nsr; DMFE_DBUG(0, "dmfe_tx_done()", 0); nsr = ior(db, DM9KS_NSR); if (nsr & 0x0c) /* 网络状态寄存器的bit[3:2]是数据包1,数据包2传输完成标志,而现在就是判断是否有一个数据包发送完毕 */ { if(nsr & 0x04) db->tx_pkt_cnt--; if(nsr & 0x08) db->tx_pkt_cnt--; if(db->tx_pkt_cnt < 0) /* 判断缓冲区是否有未发送的数据包 */ { printk(KERN_DEBUG "DM9KS:tx_pkt_cnt ERROR!! "); while(ior(db,DM9KS_TCR) & 0x1){} /* 发送控制寄存器的bit[0],请求发送数据 */ db->tx_pkt_cnt = 0; } }else{ while(ior(db,DM9KS_TCR) & 0x1){} db->tx_pkt_cnt = 0; } netif_wake_queue(dev); /* 唤醒接收队列 */ return; }  因为dm9000可以发送两个数据包,当发送一个数据包产生中断后,要确认一下队列中有没有第2个包需要发送。
    1.读取dm9000寄存器NSR(Network Status Register)获取发送的状态,存在变量tx_status中;
    2.如果发送状态为NSR_TX2END(第2个包发送完毕)或者NSR_TX1END(第1个包发送完毕),则将待发送的数据包数量(db-> tx_pkt_cnt )减1,已发送的数据包数量(dev->stats.tx_packets)加1;
    3.检查变量db-> tx_pkt_cnt(待发送的数据包)是否大于0(表明还有数据包要发送),则调用函数dm9000_send_packet发送队列中的数据包;
    4.调用函数netif_wake_queue (dev)通知内核可以将待发送的数据包进入发送队列。   上面就是我分析的全部了,同时我还想说,如果你想对这篇文章有更好的理解,或者你想对这方面的知识有更好的了解,我建议你先看一下我前面的一篇文章:网络驱动框架的介绍。或者去看一些其他人的。这个没有什么要求。只有你对网络驱动的框架了一定的解了,你再来看这篇文章就相当于是在骨骼上填肉,才能对驱动程式有一个更好的了解。而真正了解了这些才能去将这个驱动移植到一个现有的开发板上。而我会在下一篇文章中介绍移植这个驱动到S3C2440中。   参考文章: 【驱动】DM9000网卡驱动分析
  Linux DM9000网卡驱动程序完全分析
  驱动之路-DM9000网卡驱动程序分析
  DM9000 网卡驱动程序分析