网卡驱动程序
PCI初始化
当我们正确编译完我们的程序后,我们就需要把生成的目标文件加载到内核中去,加载过程使用linux命令insmod。
module_init 在 insmod之后首先执行,它直接调用了pci_module_init(),这个函数代码在Linux/drivers/net/eepro100.c中。pci_module_init ()是Linux内核提供给模块是一个
接口,在该函数里面调用了 pci_register_driver(),这个函数代码在Linux/drivers/pci/pci.c中, pci_register_driver的主要用途是内核中进行了注册该PCI驱动,内核中有一个PCI设备的大的链
,pci_register_driver负责把这个PCI驱动挂到里面去。PCI驱动程序注册信息包含如下内容:
static struct pci_driver e1000_driver = {
.name = e1000_driver_name,
.id_table = e1000_pci_tbl,
.probe = e1000_probe,
.remove = __devexit_p(e1000_remove),
#ifdef CONFIG_PM
/* Power Management Hooks */
.suspend = e1000_suspend,
.resume = e1000_resume,
#endif
#ifndef USE_REBOOT_NOTIFIER
.shutdown = e1000_shutdown,
#endif
#ifdef HAVE_PCI_ERS
.err_handler = &e1000_err_handler
#endif
};
在注册时登记的probe函数,其实就是设备的初始化函数。对于E1000,初始化函数为e1000_probe。
网卡初始化
probe函数是用来初始化整个设备和做一些准备工作。以E1000为例进行设备初始化
。
static int __devinit e1000_probe(struct pci_dev *pdev,const struct pci_device_id *ent)
{
struct net_device *netdev;
struct e1000_adapter *adapter;
....
err=pci_enable_device(pdev); //激活PCI设备
...
err=pci_set_dma_mask(pdev,DMA_64BIT_MASK); //设置pci设备的dma掩码,告诉系统自己的DMA寻址方式,是32位还是64位
...
netdev = alloc_etherdev(sizeof(struct e1000_adapter)); //为e1000网卡对应的net_device结构分配内存
...
//设备结构初始化过程
pci_set_drvdata(pdev,netdev);
adapter=netdev_priv(netdev);
adapter->netdev=netdev;
adapter->pdev=pdev;
...
//分配设备IO地址
mmio_start = pci_resource_start(pdev,0);
mmio_len = pci_resource_len(pdev,0);
....
adapter->hw.hw_addr = ioremap(mmio_start, mmio_len);
....
/*将e1000网卡驱动的相应函数注册到net_device中*/
netdev->open = &e1000_open;
netdev->stop = &e1000_close;
...
netif_napi_add(netdev,&adapter->napi,e1000_clean,64); // 注册poll函数为e1000_clean, weight为64
...
//登记设备的IO地址
netdev->mem_start = mmio_start;
netdev->mem_end = mmio_start+mmio_len;
netdev->base_addr = adapter->hw.io_base;
....
//设备参数初始化
if ((err = e1000_sw_init(adapter)))
goto err_sw_init;
if ((err = e1000_init_mac_params(&adapter->hw)))
goto err_hw_init;
if ((err = e1000_init_nvm_params(&adapter->hw)))
goto err_hw_init;
....
if(e1000e_read_mac_addr(&adapter->hw)) ndev_err(...); //从网卡设备的EEPROM中读取mac地址
memcpy(netdev->dev_addr, adapter->hw.mac.addr, netdev->addr_len);
memcpy(netdev->perm_addr, adapter->hw.mac.addr, netdev->addr_len);
....
adapter->rx_ring->count = 256; //设置接收环型缓冲区队列的缺省大小
...
INIT_WORK(&adapter->reset_task, e1000_reset_task); //延时执行初始化环型缓冲区队列任务
INIT_WORK(&adapter->watchdog_task, e1000_watchdog_task);
e1000_reset(adapter);
...
strcpy(netdev->name,"eth%d");
//设备注册
err= register_netdev(netdev); //将当前网络设备注册到系统的dev_base[]设备数组当中
....
return 0;
}
至此,该网卡设备已经就绪,可以使用了。
网卡激活
ifconfig eth0 up命令来把我们的设备激活,该命令导致我们在Probe阶段登记的
e1000_open函数的执行。主要的作用的分配接收发送数据包的数据缓冲区和中断资源。
static int e1000_open(struct net_device *netdev) {
struct e1000_adapter *adapter = netdev_priv(netdev);
....
//预先分配缓冲区资源
err = e1000_setup_all_rx_resoures(adapter)
....
/* before we allocate an interrupt, we must be ready to handle it.
* Setting DEBUG_SHIRQ in the kernel makes it fire an interrupt
* as soon as we call pci_request_irq, so we have to setup our
* clean_rx handler before we do so. */
e1000_configure(adapter);
err = e1000_request_irq(adapter); //分配irq中断
....
}
int e1000_setup_all_rx_resources(struct e1000_adapter *adapter) {
int i,err=0;
for(i=0 ; i
num_rx_queues ; i++){ //接收数据队列数量
err = e1000_setup_rx_resources(adapter,&adapter->rx_ring[i]); //分配每个队列的缓冲区
if(err){
...
}
}
return err;
}
网卡接收采用环形缓冲区接收数据。一个网卡可以设置多个环形缓冲区队列,每个环形缓冲区队列由多个描述符组成,每个描述符中都包含一个缓冲区buffer,数据存放在buffer_info->skb->data中。每个描述符都有一个状态变量以表示该缓冲区buffer是否可以被新到的数据包覆盖。
E1000环形缓冲区结构:
struct e1000_rx_ring{
void *desc; //指向该环形缓冲区
dma_addr_t dma; //dma物理地址
unsigned int size;
unsigned int count; //环形队列由多少个描述符组成,这个在probe中定义了
unsigned int next_to_use; //下一个可使用的描述符号
unsigned int next_to_clean; //该描述符状态(是否正在使用,是否脏)
struct e1000_buffer *buffer_info; //缓冲区buffer
...
}
缓冲区buffer_info->skb存放的就是接收到的数据报文的数据。
struct e1000_buffer{
struct sk_buff *skb;
....
}
e1000_setup_rx_resources函数只是分配了环形缓冲区的描述块,并未分配存放数据报文的SKB内存。
环形缓冲区的SKB内存块,通过e1000_configure进行分配,代码实现如下;在收包过程中,也会根据情况进行补充分配,在收包流程中会看到。
static void e1000_configure(struct e1000_adapter *adapter)
{
struct net_device *netdev = adapter->netdev;
int i;
....
e1000_set_multi(netdev);
e1000_configure_tx(adapter);
e1000_setup_rctl(adapter);
e1000_configure_rx(adapter); //设置环形缓冲区硬件
/* call E1000_DESC_UNUSED which always leaves
* at least 1 descriptor unused to make sure
* next_to_use != next_to_clean */
for (i = 0; i < adapter->num_rx_queues; i++) {
struct e1000_rx_ring *ring = &adapter->rx_ring[i];
//分配SKB内存
adapter->alloc_rx_buf(adapter, ring,
E1000_DESC_UNUSED(ring));
}
adapter->alloc_rx_buf在设备初始化时,指定调用的函数为e1000_alloc_rx_buffers
static void e1000_alloc_rx_buffers(struct e1000_adapter *adapter,
struct e1000_rx_ring *rx_ring,int cleaned_count) {
struct net_device *netdev = adapter->netdev;
struct pci_dev *pdev = adapter->pdev;
struct e1000_rx_desc *rx_desc;
struct e1000_buffer *buffer_info;
struct sk_buff *skb;
unsigned int i;
unsigned int bufsz = adapter->rx_buffer_len+NET_IP_ALIGN;
i=rx_ring->next_to_use;
buffer_info = &rx_ring->buffer_info[i];
while (cleaned_count--){
skb = buffer_info ->skb;
if(skb){
....
}
skb = netdev_alloc_skb(netdev,bufsz); //skb缓存的分配
if(unlikely(!skb)){
adapter->alloc_rx_buff_failed++;
break;
}
skb_reserve(skb,NET_IP_ALIGN);
buffer_info->skb = skb;
buffer_info->length = adapter ->rx_buffer_len;
map_skb:
buffer_info->dma = pci_map_single(pdev,
skb->data,
adapter->rx_buffer_len,
PCI_DMA_FROMDEVICE);
//建立DMA映射,把每一个缓冲区skb->data都映射给了设备,缓存区描述符利用dma保存
了每一次映射的地址
....
rx_desc = E1000_RX_DESC(*rx_ring, i);
rx_desc->buffer_addr = cpu_to_le64(buffer_info->dma);
if (unlikely(++i == rx_ring->count)) //达到环形缓冲区末尾
i =0 ;
buffer_info = &rx_ring->buffer_info[i];
}
if(likely(rx_ring->netx_to_use!=i)){
rx_ring->next_to_use = i;
if (unlikely(i-- == 0))
i = (rx_ring->count - 1);
...
}
}
在上述函数中 pci_map_single将分配的内核内存映射到网卡的收包缓存中。当数据包到达时,数据将通过DMA通道,直接写入系统内核内存,并发出一个硬件中断。
至此网卡可以开始收发包流程。
网卡收包流程
网卡可以采用中断方式收发数据包,linux 内核2.4以上支持另外一种收发包方式:NAPI。流程图如下:
前面说到网卡收到数据包后,将数据通过DMA通道,直接写入系统内核内存,并发出一个硬件中断。在系统初始化时,已经指定了中断处理函数,对于E1000网卡,处理函数为e1000_intr
static irqreturn_t e1000_intr(int irq, void *data) {
struct net_device *netdev = data;
struct e1000_adapter *adapter = netdev_priv(netdev);
struct e1000_hw *hw = &adapter->hw;
u32 icr = E1000_READ_REG(hw, E1000_ICR);
if (unlikely(!icr))
return IRQ_NONE; /* Not our interrupt */
adapter->total_tx_bytes = 0;
adapter->total_rx_bytes = 0;
adapter->total_tx_packets = 0;
adapter->total_rx_packets = 0;
for (i = 0; i < E1000_MAX_INTR; i++) {
rx_cleaned = 0;
for (j = 0; j < adapter->num_rx_queues; j++)
rx_cleaned |= adapter->clean_rx(adapter,
&adapter->rx_ring[j]);
tx_cleaned = 0;
for (j = 0 ; j < adapter->num_tx_queues ; j++)
tx_cleaned |= e1000_clean_tx_irq(adapter,
&adapter->tx_ring[j]);
if (!rx_cleaned && !tx_cleaned)
break;
}
if (likely(adapter->itr_setting & 3))
e1000_set_itr(adapter);
if (hw->mac.type == e1000_82547 || hw->mac.type == e1000_82547_rev_2)
e1000_irq_enable(adapter);
return IRQ_HANDLED;
}
从该函数的处理可以看到,每次中断处理时,收发包的统计仅针对本次中断的收发包进行。
e1000_set_itr(adapter)函数,将统计数据累加到网卡统计计数器中。
一次中断过程,中断函数处理所有环形队列中的待收数据包。每个数据报文的处理通过
adapter->clean_rx函数处理,在网卡初始化时,该函数指定为e1000_clean_rx_irq。
e1000_clean_rx_irq(struct e1000_adapter *adapter,struct e1000_rx_ring *rx_ring,
)
{
struct net_device *netdev = adapter->netdev;
struct pci_dev *pdev = adapter->pdev;
struct e1000_rx_desc *rx_desc,*next_rxd;
struct e1000_buffer *buffer_info, *next_buffer;
...
unsigned int i;
int cleaned_count = 0;
....
i = rx_ring->next_to_clean; //next_to_clean是下一个可以被清除的描述符索引,上面讲过环形缓冲队列由多个描述符组成,每个描述符都有一个用于存放接收数据包的缓冲区buffer,这里所说的“可以被清除”并不是将其删除,而是标记这个缓冲区的数据已经处理(可能正在处理),但是否处理完了要看rx_desc->status&E1000_RXD_STAT_DD,当有新数据需要使用缓冲区时,只是将已处理的缓冲区覆盖而已, 这里的i可以理解为可以被新数据覆盖的缓冲区序号
rx_desc = E1000_RX_DESC(*rx_ring,i); //得到相应的描述符
buffer_info = &rx_ring->buffer_info[i];
while(rx_desc->status & E1000_RXD_STAT_DD){ //测试其状态是否为已删除
struct sk_buff *skb;
u8 status;
status = rx_desc->status;
skb = buffer_info->skb; //得到缓冲区中的数据
buffer_info->skb = NULL;
prefetch(skb->data-NET_IP_ALIGN);
if(++i == rx_ring->count) //处理环形缓冲区达到队列末尾的情况,因为是环形的,所以到达末尾的下一个就是队列头,这样整个队列就不断地循环处理。然后获取下一格描述符的状态,看看是不是处理删除状态。如果处于就会将新到达的数据覆盖旧的缓冲区,如果不处于则跳出循环,并将当前缓冲区索引号置为下一次查询的目标
i = 0;
next_rxd = E1000_RX_DESC(*rx_ring,i);
next_buffer = &rx_ring->buffer_info[i];
cleaned = true ;
cleaned_count ++;
pci_unmap_single(pdev,buffer_info->dma,buffer_info->length,PCI_DMA_FROMDEVIC
E); //* 取消映射,因为通过DMA,网卡已经把数据放在了主内存中,这里一取消,也就意味着,CPU可以处理主内存中的数据了 */
....
//checksum
...
netif_rx(skb); //进入中断模式 将数据包插入接收队列中,等待软中断处理 中断模式不用环形接收缓冲队列
netdev->last_rx = jiffies;
next_desc:
rx_desc->status =0;
if(unlikely(cleaned_count >= E1000_RX_BUFFER_WRITE)){
adapter->alloc_rx_buf(adapter,rx_ring,cleaned_count); //在e1000_up中已经调用了这个函数为环形缓冲区队列中的每个缓冲区分配了sk_buff内存,但是如果接收到数据以后,调用netif_receive_skb(skb)向上提交数据以后,这段内存将始终被这个skb占用(直到上层处理完以后才会调用_kfree_skb释放),换句话说,就是当前缓冲区必须重新申请分配sk_buff内存,为下一个数据作准备
cleaned_count = 0;
}
rx_desc = next_rxd;
buffer_info = next_buffer;
}
rx_ring->next_to_clean = i;
cleaned_count = E1000_DESC_UNUSED(rx_ring);
if(cleaned_count)
adapter->alloc_rx_buf(adapter,rx_ring,cleaned_count);
...
return cleaned;
}
主要的处理流程包括,循环处理当前队列中的每个描述块。对于当前描述块,取出当前描述块包缓存的地址,并将原来包缓存指针置空。用pci_unmap_single函数取消内存映射关系,并通过netif_rx(skb)将数据包提交给协议栈,或应用进行处理。
在该函数处理中,可以看到包异常时,数据包不会往协议栈发送,系统将该描述块状态置0后,将直接提交给后续的回收函数进行内存回收。
在本次中断过程中,如果需要回收的描述块超出阀值,立刻调用内存adapter->alloc_rx_buf进行回收。
还有一点注意的是,SKB数据包由网卡驱动程序分配内存,但是由协议栈进行释放的,可以认为是个动态内存分配的方式。
中断处理流程图如下:
对于NAPI模式,流程图如下: