数据包的接收
作者:kendo Kernel:2.6.12 一、从网卡说起 这并非是一个网卡驱动分析的专门文档,只是对网卡处理数据包的流程进行一个重点的分析。这里以Intel的e100驱动为例进行分析。 大多数网卡都是一个PCI设备,PCI设备都包含了一个标准的配置寄存器,寄存器中,包含了PCI设备的厂商ID、设备ID等等信息,驱动 程序使用来描述这些寄存器的标识符。如下:
CODE:
struct pci_device_id {
__u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/ __u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */ __u32 class, class_mask; /* (class,subclass,prog-if) triplet */ kernel_ulong_t driver_data; /* Data private to the driver */ }; 这样,在驱动程序中,常常就可以看到定义一个struct pci_device_id 类型的数组,告诉内核支持不同类型的 PCI设备的列表,以e100驱动为例: #define INTEL_8255X_ETHERNET_DEVICE(device_id, ich) {/ PCI_VENDOR_ID_INTEL, device_id, PCI_ANY_ID, PCI_ANY_ID, / PCI_CLASS_NETWORK_ETHERNET << 8, 0xFFFF00, ich } static struct pci_device_id e100_id_table[] = { INTEL_8255X_ETHERNET_DEVICE(0x1029, 0), INTEL_8255X_ETHERNET_DEVICE(0x1030, 0), INTEL_8255X_ETHERNET_DEVICE(0x1031, 3), ……/*略过一大堆支持的设备*/ { 0, } }; 在内核中,一个PCI设备,使用struct pci_driver结构来描述, struct pci_driver { struct list_head node; char *name; struct module *owner; const struct pci_device_id *id_table; /* must be non-NULL for probe to be called */ int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */ void (*remove) (struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */ int (*suspend) (struct pci_dev *dev, pm_message_t state); /* Device suspended */ int (*resume) (struct pci_dev *dev); /* Device woken up */ int (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable); /* Enable wake event */ void (*shutdown) (struct pci_dev *dev); struct device_driver driver; struct pci_dynids dynids; }; 因为在系统引导的时候,PCI设备已经被识别,当内核发现一个已经检测到的设备同驱动注册的id_table中的信息相匹配时, 它就会触发驱动的probe函数,以e100为例: /* * 定义一个名为e100_driver的PCI设备 * 1、设备的探测函数为e100_probe; * 2、设备的id_table表为e100_id_table */ static struct pci_driver e100_driver = { .name = DRV_NAME, .id_table = e100_id_table, .probe = e100_probe, .remove = __devexit_p(e100_remove), #ifdef CONFIG_PM .suspend = e100_suspend, .resume = e100_resume, #endif .driver = { .shutdown = e100_shutdown, } }; 这样,如果系统检测到有与id_table中对应的设备时,就调用驱动的probe函数。 驱动设备在init函数中,调用pci_module_init函数初始化PCI设备e100_driver: static int __init e100_init_module(void) { if(((1 << debug) - 1) & NETIF_MSG_DRV) { printk(KERN_INFO PFX "%s, %s/n", DRV_DESCRIPTION, DRV_VERSION); printk(KERN_INFO PFX "%s/n", DRV_COPYRIGHT); } return pci_module_init(&e100_driver); } 一切顺利的话,注册的e100_probe函数将被内核调用,这个函数完成两个重要的工作: 1、分配/初始化/注册网络设备; 2、完成PCI设备的I/O区域的分配和映射,以及完成硬件的其它初始化工作; 网络设备使用struct net_device结构来描述,这个结构非常之大,许多重要的参考书籍对它都有较为深入的描述,可以参考《Linux设备驱动程序》中网卡驱动设计的相关章节。我会在后面的内容中,对其重要的成员进行注释; 当probe函数被调用,证明已经发现了我们所支持的网卡,这样,就可以调用register_netdev函数向内核注册网络设备了,注册之前,一般会调用alloc_etherdev为以太网分析一个net_device,然后初始化它的重要成员。 除了向内核注册网络设备之外,探测函数另一项重要的工作就是需要对硬件进行初始化,比如,要访问其I/O区域,需要为I/O区域分配内存区域,然后进行映射,这一步一般的流程是: 1、request_mem_region() 2、ioremap() 对于一般的PCI设备而言,可以调用: 1、pci_request_regions() 2、ioremap() pci_request_regions函数对PCI的6个寄存器都会调用资源分配函数进行申请(需要判断是I/O端口还是I/O内存),例如:
CODE:
int pci_request_regions(struct pci_dev *pdev, char *res_name)
{ int i; for (i = 0; i < 6; i++) if(pci_request_region(pdev, i, res_name)) goto err_out; return 0;
CODE:
int pci_request_region(struct pci_dev *pdev, int bar, char *res_name)
{ if (pci_resource_len(pdev, bar) == 0) return 0; if (pci_resource_flags(pdev, bar) & IORESOURCE_IO) { if (!request_region(pci_resource_start(pdev, bar), pci_resource_len(pdev, bar), res_name)) goto err_out; } else if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) { if (!request_mem_region(pci_resource_start(pdev, bar), pci_resource_len(pdev, bar), res_name)) goto err_out; } return 0; 有了这些基础,我们来看设备的探测函数: static int __devinit e100_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { struct net_device *netdev; struct nic *nic; int err; /*分配网络设备*/ if(!(netdev = alloc_etherdev(sizeof(struct nic)))) { if(((1 << debug) - 1) & NETIF_MSG_PROBE) printk(KERN_ERR PFX "Etherdev alloc failed, abort./n"); return -ENOMEM; } /*设置各成员指针函数*/ netdev->open = e100_open; netdev->stop = e100_close; netdev->hard_start_xmit = e100_xmit_frame; netdev->get_stats = e100_get_stats; netdev->set_multicast_list = e100_set_multicast_list; netdev->set_mac_address = e100_set_mac_address; netdev->change_mtu = e100_change_mtu; netdev->do_ioctl = e100_do_ioctl; SET_ETHTOOL_OPS(netdev, &e100_ethtool_ops); netdev->tx_timeout = e100_tx_timeout; netdev->watchdog_timeo = E100_WATCHDOG_PERIOD; netdev->poll = e100_poll; netdev->weight = E100_NAPI_WEIGHT; #ifdef CONFIG_NET_POLL_CONTROLLER netdev->poll_controller = e100_netpoll; #endif /*设置网络设备名称*/ strcpy(netdev->name, pci_name(pdev)); /*取得设备私有数据结构*/ nic = netdev_priv(netdev); /*网络设备指针,指向自己*/ nic->netdev = netdev; /*PCIy设备指针,指向自己*/ nic->pdev = pdev; nic->msg_enable = (1 << debug) - 1; /*将PCI设备的私有数据区指向网络设备*/ pci_set_drvdata(pdev, netdev); /*激活PCI设备*/ if((err = pci_enable_device(pdev))) { DPRINTK(PROBE, ERR, "Cannot enable PCI device, aborting./n"); goto err_out_free_dev; } /*判断I/O区域是否是I/O内存,如果不是,则报错退出*/ if(!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) { DPRINTK(PROBE, ERR, "Cannot find proper PCI device " "base address, aborting./n"); err = -ENODEV; goto err_out_disable_pdev; } /*分配I/O内存区域*/ if((err = pci_request_regions(pdev, DRV_NAME))) { DPRINTK(PROBE, ERR, "Cannot obtain PCI resources, aborting./n"); goto err_out_disable_pdev; } /* * 告之内核自己的DMA寻址能力,这里不是很明白,因为从0xFFFFFFFF来看,本来就是内核默认的32了 * 为什么还要调用pci_set_dma_mask来重复设置呢?可能是对ULL而非UL不是很了解吧。 */ if((err = pci_set_dma_mask(pdev, 0xFFFFFFFFULL))) { DPRINTK(PROBE, ERR, "No usable DMA configuration, aborting./n"); goto err_out_free_res; } SET_MODULE_OWNER(netdev); SET_NETDEV_DEV(netdev, &pdev->dev); /*分配完成后,映射I/O内存*/ nic->csr = ioremap(pci_resource_start(pdev, 0), sizeof(struct csr)); if(!nic->csr) { DPRINTK(PROBE, ERR, "Cannot map device registers, aborting./n"); err |
Linux TCP/IP协议栈笔记
本文深入探讨Linux网络驱动中TCP/IP协议栈的中断处理机制,从设备的open函数开始,讲解中断注册、网卡中断处理、数据接收流程,包括NAPI技术的应用,以及中断与轮询技术的差异。文章通过代码分析展示了如何从网卡接收数据,如何处理中断,以及如何将数据包交给上层协议栈。
摘要由CSDN通过智能技术生成