Linux驱动修炼之道-DM9000A网卡驱动框架源码分析(下)
努力成为linux kernel hacker的人李万鹏原创作品,为梦而战。转载请标明出处
http://blog.csdn.net/woshixingaaa/archive/2011/06/18/6552911.aspx
这里主要讨论中断处理和数据的发送和接收,这个也是网卡驱动最重要的部分了。
中断处理函数:
发生中断的情况有3中:1)接收到数据2)发送完数据3)链路状态改变
static irqreturn_t dm9000_interrupt(int irq, void *dev_id) { struct net_device *dev = dev_id; board_info_t *db = netdev_priv(dev); int int_status; unsigned long flags; u8 reg_save; dm9000_dbg(db, 3, "entering %s/n", __func__); /* A real interrupt coming */ /*保存中断到flags中*/ spin_lock_irqsave(&db->lock, flags); /*保存寄存器地址*/ reg_save = readb(db->io_addr); /*禁止DM9000的所有中断*/ iow(db, DM9000_IMR, IMR_PAR); /*获得DM9000的中断状态*/ int_status = ior(db, DM9000_ISR); /* Got ISR */ iow(db, DM9000_ISR, int_status); /* Clear ISR status */ if (netif_msg_intr(db)) dev_dbg(db->dev, "interrupt status %02x/n", int_status); /*检查Interrupt Status Register的第0位,看有没有接收数据*/ if (int_status & ISR_PRS) dm9000_rx(dev); /*检查Interrupt Status Register的第1位,看有没有发送完数据*/ if (int_status & ISR_PTS) dm9000_tx_done(dev, db); if (db->type != TYPE_DM9000E) { /*检查Interrupt Status Register的第5位,看链路状态有没有变化*/ if (int_status & ISR_LNKCHNG) { /* fire a link-change request */ schedule_delayed_work(&db->phy_poll, 1); } } /*重新使能相应中断*/ iow(db, DM9000_IMR, db->imr_all); /*还原原先的io地址*/ writeb(reg_save, db->io_addr); /*还原中断状态*/ spin_unlock_irqrestore(&db->lock, flags); return IRQ_HANDLED; }
下面说一下DM9000A中的存储部分,DM9000A内部有一个4K Dword SRAM,其中3KB是作为发送,16KB作为接收,如下图所示。其中0x0000~0x0BFF是传说中的TX buffer(TX buffer中只能存放两个包),0x0C00~0x3FFF是RX buffer。因此在写内存操作时,当IMR的第7位被设置,如果到达了地址的结尾比如到了3KB,则回卷到0。相似的方式,在读操作中,当IMR的第7位被设置如果到达了地址的结尾比如16K,则回卷到0x0C00。
那么传说中的发送函数又是哪个呢,在probe函数里进行了初始化函数指针操作。
ndev->open = &dm9000_open; ndev->hard_start_xmit = &dm9000_start_xmit; ndev->tx_timeout = &dm9000_timeout; ndev->watchdog_timeo = msecs_to_jiffies(watchdog); ndev->stop = &dm9000_stop; ndev->set_multicast_list = &dm9000_hash_table; ndev->ethtool_ops = &dm9000_ethtool_ops; ndev->do_ioctl = &dm9000_ioctl;
可以看到当上层调用hard_start_xmit时,在我们的驱动程序中实际调用的是dm9000_start_xmit,下面来分析一dm9000_start_xmit的源码。
发送函数:
/* * Hardware start transmission. * Send a packet to media from the upper layer. */ static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev) { unsigned long flags; /*获得ndev的私有数据,也就是芯片相关的信息*/ board_info_t *db = netdev_priv(dev); dm9000_dbg(db, 3, "%s:/n", __func__); /只能存放两个,如果已经有两个就退出/ if (db->tx_pkt_cnt > 1) return 1; spin_lock_irqsave(&db->lock, flags); /* *MWCMD是Memory data write command with address increment Register(F8H) *写数据到TX SRAM *写这个命令后,根据IO操作模式(8-bit or 16-bit)来增加写指针1或2 */ writeb(DM9000_MWCMD, db->io_addr); /*把数据从sk_buff中拷贝到TX SRAM中*/ (db->outblk)(db->io_data, skb->data, skb->len); /*为了统计发送了多少个字节,这个在使用ifconfig中显示出的那个发送了多少个字节就是这里计算的*/ dev->stats.tx_bytes += skb->len; /*待发送的计数加一*/ db->tx_pkt_cnt++; /*如果只有一个要发送就立即发送,如果这是第二个就应该进行排队了*/ if (db->tx_pkt_cnt == 1) { /*把数据的长度填到TXPLL(发送包长度低字节)和TXPLH(发送包长度高字节)中*/ iow(db, DM9000_TXPLL, skb->len); iow(db, DM9000_TXPLH, skb->len >> 8); /*置发送控制寄存器(TX Control Register)的发送请求位TXREQ(Auto clears after sending completely),这样就可以发送出去了*/ iow(db, DM9000_TCR, TCR_TXREQ); /* Cleared after TX complete */ /* *记下此时的时间,这里起一个时间戳的作用,之后的超时会用到。如果当前的系统时间超过设备的trans_start时间 *至少一个超时周期,网络层将最终调用驱动程序的tx_timeout。那个这个"一个超时周期"又是什么呢?这个是我们在 *probe函数中设置的,ndev->watchdog_timeo = msecs_to_jiffies(watchdog); */ dev->trans_start = jiffies; /* save the time stamp */ } else { /*如果是第二个包则把skb->len复制给队列的queue_pkt_len,然后告诉上层停止发送队列,因为发送队列已经满了*/ db->queue_pkt_len = skb->len; netif_stop_queue(dev); } spin_unlock_irqrestore(&db->lock, flags); /*每个数据包写入网卡SRAM后都要释放skb*/ dev_kfree_skb(skb); return 0; }
下面来看一下刚才提到的那个超时函数,发送超时函数:
/* Our watchdog timed out. Called by the networking layer */ static void dm9000_timeout(struct net_device *dev) { board_info_t *db = netdev_priv(dev); u8 reg_save; unsigned long flags; /* Save previous register address */ reg_save = readb(db->io_addr); spin_lock_irqsave(&db->lock, flags); netif_stop_queue(dev); dm9000_reset(db); dm9000_init_dm9000(dev); /* We can accept TX packets again */ dev->trans_start = jiffies; netif_wake_queue(dev); /* Restore previous register address */ writeb(reg_save, db->io_addr); spin_unlock_irqrestore(&db->lock, flags); }
这个函数首先停止了发送队列,然后复位dm9000,初始化dm9000,重新设置了时间戳,然后唤醒发送队列,通知网络子系统可再次传输数据包。
发送完成后的中断处理函数:
/* * DM9000 interrupt handler * receive the packet to upper layer, free the transmitted packet */ static void dm9000_tx_done(struct net_device *dev, board_info_t *db) { /*从网络状态寄存器(Network Status Register)中获得发送状态*/ int tx_status = ior(db, DM9000_NSR); /*如果发送状态为NSR_TX2END(第二个包发送完毕)或NSR_TX1END(第一个包发送完毕)*/ if (tx_status & (NSR_TX2END | NSR_TX1END)) { /*如果一个数据包发送完,待发送数据包个数减1*/ db->tx_pkt_cnt--; /*如果一个数据包发送完,已发送数据包个数加1*/ dev->stats.tx_packets++; if (netif_msg_tx_done(db)) dev_dbg(db->dev, "tx done, NSR %02x/n", tx_status); /*如果还有数据包要发送*/ if (db->tx_pkt_cnt > 0) { /*将发送的长度放到TXPLL,TXPLH寄存器中*/ iow(db, DM9000_TXPLL, db->queue_pkt_len); iow(db, DM9000_TXPLH, db->queue_pkt_len >> 8); /*置发送请求位*/ iow(db, DM9000_TCR, TCR_TXREQ); /*保存时间戳*/ dev->trans_start = jiffies; } /*通知内核将待发送数据包放入发送队列*/ netif_wake_queue(dev); } }
接收函数:
每接受到一个包,DM9000都会在包的前面加上4个字节,"01H",status与RSR(RX Status Register)的内容相同,"LENL"(数据包长度低位位),"LENH"(数据包长度高8位)。所以首先要读取这4个字节来确定数据包的状态,第一个字节"01H"表示接下来的是有效的数据包,"00H"表示没有数据包,若为其他值则表示网卡没有正确初始化,需要从新初始化。这4个字节封装在一起:
struct dm9000_rxhdr { u8 RxPktReady; u8 RxStatus; __le16 RxLen; } __attribute__((__packed__));
清晰一些如下图:
接收函数如下:
/* * Received a packet and pass to upper layer */ static void dm9000_rx(struct net_device *dev) { board_info_t *db = netdev_priv(dev); struct dm9000_rxhdr rxhdr; struct sk_buff *skb; u8 rxbyte, *rdptr; bool GoodPacket; int RxLen; /* Check packet ready or not */ do { /*MRCMDX为内存数据预取读命令,并且没有地址增加,读数据的第一个字节,直到读到0x01(数据有效)为止。*/ ior(db, DM9000_MRCMDX); /* Dummy read */ /*获得数据*/ rxbyte = readb(db->io_data); /*因为只能为0x00或0x01,所以如果大于0x01,则返回*/ if (rxbyte > DM9000_PKT_RDY) { dev_warn(db->dev, "status check fail: %d/n", rxbyte); /*停止设备*/ iow(db, DM9000_RCR, 0x00); /*停止中断请求*/ iow(db, DM9000_ISR, IMR_PAR); return; } /*如果为0x00,则返回*/ if (rxbyte != DM9000_PKT_RDY) return; /*如果有有效数据包,设置标志标量*/ GoodPacket = true; /*MRCMD是地址增加的内存数据读命令*/ writeb(DM9000_MRCMD, db->io_addr); /*读取RX SRAM中的数据放入struct dm9000_rxhdr中*/ (db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr)); /*将一个无符号的26位小头数值转换成CPU使用的值*/ RxLen = le16_to_cpu(rxhdr.RxLen); if (netif_msg_rx_status(db)) dev_dbg(db->dev, "RX: status %02x, length %04x/n", rxhdr.RxStatus, RxLen); /*一个数据包的长度应大于64字节*/ if (RxLen < 0x40) { GoodPacket = false; if (netif_msg_rx_err(db)) dev_dbg(db->dev, "RX: Bad Packet (runt)/n"); } /*一个数据包的长度应小于1536字节*/ if (RxLen > DM9000_PKT_MAX) { dev_dbg(db->dev, "RST: RX Len:%x/n", RxLen); } /* rxhdr.RxStatus is identical to RSR register. */ if (rxhdr.RxStatus & (RSR_FOE | RSR_CE | RSR_AE | RSR_PLE | RSR_RWTO | RSR_LCS | RSR_RF)) { GoodPacket = false; if (rxhdr.RxStatus & RSR_FOE) { if (netif_msg_rx_err(db)) dev_dbg(db->dev, "fifo error/n"); dev->stats.rx_fifo_errors++; } if (rxhdr.RxStatus & RSR_CE) { if (netif_msg_rx_err(db)) dev_dbg(db->dev, "crc error/n"); dev->stats.rx_crc_errors++; } if (rxhdr.RxStatus & RSR_RF) { if (netif_msg_rx_err(db)) dev_dbg(db->dev, "length error/n"); dev->stats.rx_length_errors++; } } /* Move data from DM9000 */ if (GoodPacket /*分配一个套接字缓冲区*/ && ((skb = dev_alloc_skb(RxLen + 4)) != NULL)) { skb_reserve(skb, 2); rdptr = (u8 *) skb_put(skb, RxLen - 4); /**/ (db->inblk)(db->io_data, rdptr, RxLen); dev->stats.rx_bytes += RxLen; /*以太网支持代码导出了辅助函数eth_type_trans,用来查找填入protocol中的正确值*/ skb->protocol = eth_type_trans(skb, dev); /*将套接字缓冲区发向上层*/ netif_rx(skb); /*增加发送包的引用计数*/ dev->stats.rx_packets++; } else { /*如果该数据包是坏的,则清理该数据包*/ (db->dumpblk)(db->io_data, RxLen); } } while (rxbyte == DM9000_PKT_RDY); }
如下图链路层包的传输过程:
在netif_rx中会调用napi_schedule,然后该函数又会去调用__napi_schedule()。在函数__napi_schedule()中会去调用设备的poll函数,它是设备自己注册的。在设备的poll函数中,会去调用netif_receive_skb函数,在该函数中有下面一条语句pt_prev->func,此处的func为一个函数指针,在之前的注册中设置为ip_rcv。因此,就完成了从链路层上传到网络层的这一个过程了。