Linux网卡驱动架构分析
一、网卡驱动架构
由上到下层次依次为:应用程序→系统调用接口→协议无关接口→网络协议栈→设备无关接口→设备驱动。
二、重要数据结构
1、Linux内核中每一个网卡由一个net_device结构来描述。
2、网卡操作函数集:net_device_ops,这个数据结构是上面net_device的一个成员。
3、网络数据包:sk_buff。
三、网卡驱动代码分析
所用文件为cs89x0.c,主要分析三个部分:网卡初始化、发送数据、接收数据。
㈠网卡初始化
网卡驱动初始化主要在函数init_module中完成,部分代码如下:
int __init init_module(void) { struct net_device *dev = alloc_etherdev(sizeof(struct net_local)); struct net_local *lp; int ret = 0;
...
dev->irq = irq;
dev->base_addr = io;
...
ret = cs89x0_probe1(dev, io, 1);
...
}
cs89x0_probe1函数部分代码如下:
static int __init cs89x0_probe1(struct net_device *dev, int ioaddr, int modular) { struct net_local *lp = netdev_priv(dev); static unsigned version_printed; int i; int tmp; unsigned rev_type = 0; int eeprom_buff[CHKSUM_LEN]; int retval;
...
writeword(ioaddr, ADD_PORT, PP_ChipID);
tmp = readword(ioaddr, DATA_PORT); //对硬件的初始化
...
for (i = 0; i < ETH_ALEN/2; i++) //初始化MAC地址
{
dev->dev_addr[i*2] = eeprom_buff[i];
dev->dev_addr[i*2+1] = eeprom_buff[i] >> 8;
}
...
dev->netdev_ops = &net_ops; //初始化netdev_ops
...
retval = register_netdev(dev); //注册网卡驱动
}
由代码可以看出
1、定义并分配net_device结构,使用alloc_etherdev函数。
2、初始化net_device。(包括中断号、I/O基地址、MAC地址、netdev_ops)
3、初始化硬件
4、将网卡驱动注册到内核,使用函数register_netdev
㈡发送数据
初始化netdev_ops时将其赋值为&net_ops,可在这个结构中找到发送函数
static const struct net_device_ops net_ops = { .ndo_open = net_open, .ndo_stop = net_close, .ndo_tx_timeout = net_timeout, .ndo_start_xmit = net_send_packet, .ndo_get_stats = net_get_stats, .ndo_set_multicast_list = set_multicast_list, .ndo_set_mac_address = set_mac_address, #ifdef CONFIG_NET_POLL_CONTROLLER .ndo_poll_controller = net_poll_controller, #endif .ndo_change_mtu = eth_change_mtu, .ndo_validate_addr = eth_validate_addr, };
net_send_packet代码如下:
static netdev_tx_t net_send_packet(struct sk_buff *skb,struct net_device *dev) { struct net_local *lp = netdev_priv(dev); unsigned long flags; if (net_debug > 3) { printk("%s: sent %d byte packet of type %x\n", dev->name, skb->len, (skb->data[ETH_ALEN+ETH_ALEN] << 8) | skb->data[ETH_ALEN+ETH_ALEN+1]); }
spin_lock_irqsave(&lp->lock, flags); netif_stop_queue(dev); /* initiate a transmit sequence */ writeword(dev->base_addr, TX_CMD_PORT, lp->send_cmd); writeword(dev->base_addr, TX_LEN_PORT, skb->len); /* Test to see if the chip has allocated memory for the packet */ if ((readreg(dev, PP_BusST) & READY_FOR_TX_NOW) == 0) { spin_unlock_irqrestore(&lp->lock, flags); if (net_debug) printk("cs89x0: Tx buffer not free!\n"); return NETDEV_TX_BUSY; } /* Write the contents of the packet */ writewords(dev->base_addr, TX_FRAME_PORT,skb->data,(skb->len+1) >>1); spin_unlock_irqrestore(&lp->lock, flags); dev->stats.tx_bytes += skb->len; dev_kfree_skb (skb); return NETDEV_TX_OK; }
这部分代码做了这些事情(红色高亮部分)
1、通知上层协议停止向网卡发送数据
由于网卡现在要向外发送数据包,所以要停止接收数据包
2、将skb中的数据写入寄存器中并发送走
3、释放skb空间
但是到这里并不算完,如果这就完了上层协议还是无法向网卡发送数据,网卡不能正常工作,显然这是不正常的。那么在什么地方重新允许上层协议向网卡发送数据包呢?
其实,当网卡发送走一个数据包后,会进入网卡中断程序中,查找request_irq的知中断处理程序名称为net_interrupt
static irqreturn_t net_interrupt(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
struct net_local *lp;
int ioaddr, status;
int handled = 0;
ioaddr = dev->base_addr;
lp = netdev_priv(dev);
while ((status = readword(dev->base_addr, ISQ_PORT)))
{
switch(status & ISQ_EVENT_MASK)
{
...
case ISQ_TRANSMITTER_EVENT:
dev->stats.tx_packets++;
netif_wake_queue(dev); /* Inform upper layers. */
if ((status & ( TX_OK |
TX_LOST_CRS |
TX_SQE_ERROR |
TX_LATE_COL |
TX_16_COL)) != TX_OK) {
if ((status & TX_OK) == 0)
dev->stats.tx_errors++;
if (status & TX_LOST_CRS)
dev->stats.tx_carrier_errors++;
if (status & TX_SQE_ERROR)
dev->stats.tx_heartbeat_errors++;
if (status & TX_LATE_COL)
dev->stats.tx_window_errors++;
if (status & TX_16_COL)
dev->stats.tx_aborted_errors++;
}
break;
...
}
}
}
4、通知上层协议,可以向网卡发送数据包。使用函数netif_wake_queue
㈢数据接收
当网卡接受到一个数据包后,进入网卡中断处理程序
static irqreturn_t net_interrupt(int irq, void *dev_id) { struct net_device *dev = dev_id; struct net_local *lp; int ioaddr, status; int handled = 0; ioaddr = dev->base_addr; lp = netdev_priv(dev); while ((status = readword(dev->base_addr, ISQ_PORT))) { switch(status & ISQ_EVENT_MASK) { ...
case ISQ_RECEIVER_EVENT:
/* Got a packet(s). */
net_rx(dev);
break;
}
}
}
net_rx函数代码如下
static void net_rx(struct net_device *dev) { struct sk_buff *skb; int status, length; int ioaddr = dev->base_addr; status = readword(ioaddr, RX_FRAME_PORT); length = readword(ioaddr, RX_FRAME_PORT); if ((status & RX_OK) == 0) { count_rx_errors(status, dev); return; } /* Malloc up new buffer. */ skb = dev_alloc_skb(length + 2); if (skb == NULL) { #if 0 /* Again, this seems a cruel thing to do */ printk(KERN_WARNING "%s: Memory squeeze, dropping packet.\n", dev->name); #endif dev->stats.rx_dropped++; return; } skb_reserve(skb, 2); /* longword align L3 header */ readwords(ioaddr, RX_FRAME_PORT, skb_put(skb, length), length >> 1); if (length & 1) skb->data[length-1] = readword(ioaddr, RX_FRAME_PORT); if (net_debug > 3) { printk( "%s: received %d byte packet of type %x\n", dev->name, length, (skb->data[ETH_ALEN+ETH_ALEN] << 8) | skb->data[ETH_ALEN+ETH_ALEN+1]); } skb->protocol=eth_type_trans(skb,dev); netif_rx(skb); dev->stats.rx_packets++; dev->stats.rx_bytes += length; }
由代码可以看出:
1、读取接收状态
2、读取接收到数据的长度
3、分配skb结构,skb = dev_alloc_skb(length + 2);
4、从硬件寄存器中读取数据存入skb
5、江封装好的数据包向上发送给协议栈,使用函数netif_rx
网卡驱动架构到这里大致就分析完了。如果有疑问或错误,欢迎指出。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。