首页 网卡驱动程序

网卡驱动程序

举报
开通vip

网卡驱动程序网卡驱动程序 PCI初始化 当我们正确编译完我们的程序后,我们就需要把生成的目标文件加载到内核中去,加载过程使用linux命令insmod。 module_init 在 insmod之后首先执行,它直接调用了pci_module_init(),这个函数代码在Linux/drivers/net/eepro100.c中。pci_module_init ()是Linux内核提供给模块是一个标准接口,在该函数里面调用了 pci_register_driver(),这个函数代码在Linux/drivers/pci/pc...

网卡驱动程序
网卡驱动程序 PCI初始化 当我们正确编译完我们的程序后,我们就需要把生成的目标文件加载到内核中去,加载过程使用linux命令insmod。 module_init 在 insmod之后首先执行,它直接调用了pci_module_init(),这个函数代码在Linux/drivers/net/eepro100.c中。pci_module_init ()是Linux内核提供给模块是一个 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 接口,在该函数里面调用了 pci_register_driver(),这个函数代码在Linux/drivers/pci/pci.c中, pci_register_driver的主要用途是内核中进行了注册该PCI驱动,内核中有一个PCI设备的大的链 关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf ,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 ; inum_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模式,流程图如下:
本文档为【网卡驱动程序】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_079973
暂无简介~
格式:doc
大小:89KB
软件:Word
页数:16
分类:互联网
上传时间:2017-09-01
浏览量:26