您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 管理学资料 > 第六章:PCI子系统与网卡由于PCI设备在x86架构以
第六章:PCIPCIPCIPCI子系统与网卡由于PCI设备在x86架构以及其他架构的系统中都很流行,所以我们会花一些篇幅描述内核如何管理这些设备,重点描述网络设备。这一章的内容会帮助你理解第八章中有关设备注册的内容。你也会了解到PCI是如何完成诸如设备探测,电源管理等功能。如果想要更深入了解PCI,比如有关PCI设备的设计,实现等细节,你可以参考LinuxDeviceDrivers和UnderstandingtheLinuxKernel或者是PCI规范。PCI子系统(也可以称作PCI层)提供了内核中不同PCI设备所公用的函数。这个子系统减轻了不同设备驱动程序员的工作负担,使得PCI驱动可以被组织成一致的风格。当然,这也使得内核收集和管理设备的信息变得容易了,这些信息包括审计信息和一些计数器。在本章中,我们将讨论一些在PCI子系统中使用的关键数据结构,以及这些数据结构在一个典型的PCI设备驱动中如何初始化的。我也会简单的描述一下PCI电源管理以及Wake-On-LAN功能。6.16.16.16.1本章所要描述的数据结构下面是一些在PCI子系统中会用到的数据结构。当然,内核中有许多PCI相关的数据结构,但是下面这些对理解本书中的内容是必须的。第一个数据结构是在include/linux/mod_devicetable.h中定义的,其他两个在include/linux/pci.h中定义。pci_device_id设备标识号。这不是Linux中定义的ID,而是在PCI标准中定义的ID。在下一节“注册PCI网卡“中会描述这个ID的定义,在后面的“注册PCI设备的例子“一节中会展示一个例子。pci_dev每个PCI设备都会分配一个pci_dev实例,就像每个网络设备都会分配一个net_device实例。内核使用这个数据结构来引用一个PCI设备。pci_driver这个数据结构定义了PCI子系统和PCI设备之间的接口。这个数据结构中有很多函数指针。所有的PCI设备都会用到这个结构。在“注册PCI设备的例子”一节中会看到它的定义,以及它如何初始化的例子。pci_driver结构定义了一个PCI设备驱动。下面描述了它的主要成员变量,重点是网络设备相关的变量。相关的函数指针在不同的设备驱动中初始化成相应的函数。char*name设备的名称conststructpci_device_id*id_table内核用这个ID数组关联相应的设备。在“注册PCI设备的例子”一节中会看到相应的例子。int(*probe)(structpci_dev*dev,conststructpci_device_id*id)当PCI子系统通过PCIId数组找到相应的PCI设备驱动时会调用这个函数。这个函数使能相应的硬件,分配net_device结构,初始化并且注册新设备。在驱动中还会分配其他一些数据结构(比如发送和接收网络报的缓冲区),这些数据结构在设备中会用到。[*]第八章会描述网络设备注册。void(*remove)(structpci_dev*dev)当从系统中删除一个设备或者热插扒设备被拔下时,PCI子系统会调用这个函数。这个函数与probe函数相对应,在这个函数中会释放相应的数据结构。网络设备驱动调用这个函数释放设备初始化时分配的I/O端口和I/O内存,释放net_device结构以及辅助的一些数据结构,这些数据结构通常是在probe函数中分配的。int(*suspend)(structpci_dev*dev,pm_message_tstate)int(*resume)(structpci_dev*dev)这个函数在设备在休眠状态和激活状态之间切换时调用。请参考后面的“电源管理和Wake-On-LAN”一节的内容。int(*enable_wake)(structpci_dev*dev,u32state,intenable)通过这个函数,设备驱动可以生成电源管理信号来激活或者关闭系统。请参考“电源管理和Wake-on-LAN“一节。structpci_dynidsdynids动态id,请看下一节。在下一节中,有一个pci_driver初始化的例子。6.26.26.26.2注册一个PCIPCIPCIPCI网络设备PCI设备通常由一组参数唯一的标识,这些参数包括,设备厂商,型号等。这些参数保存在pci_device_id结构中,如下所示:structpci_device_id{unsignedintvendor,device;unsignedintsubvendor,subdevice;unsignedintclass,class_mask;unsignedlongdriver_data;};这些参数的意义都很明显。vendor和device已经足够唯一标识一个设备。subvendor和subdevice很少会使用到,所以通常会被置成匹配任意设备(PCI_ANY_ID)。class和class_mark表示这个设备属于哪个类别。本章我们主要讨论NETWORK类。driver_data不是PCIID的一部分,这是设备使用的私有参数。每个设备驱动都会向内核注册一个pci_device_id的实例,它列出了设备驱动能够处理的所有PCI设备的ID。PCI设备驱动调用函数pci_register_device和pci_unregister_driver来向内核注册自己。这些函数的定义在drivers/pci/pci.c里面。函数pci_module_init与pci_register_device等同。有一些驱动任使用函数pci_module_init来注册设备。这些都是在内核引入pci_register_driver函数之前就存在的设备驱动。pci_register_driver使用pci_driver做为参数。通过pci_driver的id_table数组,内核就知道这个驱动能够管理哪些设备;通过pci_driver里面定义的函数,内核就知道如何调用驱动与设备交互。使用PCI设备的一个好处就是它可以自动探测设备所使用的中断和其他一些资源。这些参数可以在模块加载时指定,但是大多数情况下,让驱动自己探测可能会更方便一下。当然,在必要的情况下,用户还是可以手动指定这些参数。我们不讨论内核是如何通过设备ID来探测PCI设备的细节。但是,值得注意的是,通常有两种探测方法:静态给定一个设备的PCIID,内核可以通过查找设备驱动的id_table数组完成探测。这种方法叫做静态探测。动态这种方法是通过用户输入的设备ID来探测。这个方法很少被使用,通常用于设备调试。动态的意思是系统管理员可以添加一个设备ID,而不是设备id可以自动地改变。6.3.6.3.6.3.6.3.电源管理和Wake-On-LANPCI的电源管理事件由pci_driver中的suspend和resume两个函数处理。除了维护设备的状态,这两个函数在以下情况下需要执行特殊的动作:�suspend它会停止设备的发送队列,设备不允许发送包�resume重新使能设备的发送对了,设备可以发送包Wake-on-Lan(WOL)功能允许网卡在收到一种特定类型的帧时唤醒处在休眠模式的系统。WOL缺省是关闭的。这个功能可以通过pci_enable_wake来开启或关闭。最开始引入WOL功能时,只用一种类型的帧可以唤醒系统:魔术帧,这种类型的帧有两个主要的特点:[*]WOL是AMD发明的,最开始的名称是魔术包技术�目的mac是接收网卡的mac地址(不管是单播,多播还是广播)�在帧的载荷里面,从任意一个地方开始,有连续6个字节是FF(FF:FF:FF:FF:FF:FF),后面跟的是重复16次的目的网卡的MAC地址现在的规范允许任意包唤醒系统。一个使用方便的设备可以在加载设备模块时通过参数来开启或者关闭WOL功能(例如drivers/net/3c59x)。ethtool允许管理员配置哪种帧可以唤醒系统。有一个选择是ARP包。详细的内容请参考第28章的“Wake-on-LAN事件。net-utils包里面有一个命名,ether-wake,可以用来生成WOL的以太网帧。当一个WOL使能的设备收到唤醒系统的帧时,它会生成一个电源管理事件来通知系统。有关电源管理的细节,请参考第8章的”与电源管理的交互“一节的内容。6.4.6.4.6.4.6.4.PCIPCIPCIPCI网络设备注册的例子我们以IntelPRO/100的驱动为例来详细描述设备注册的过程:#defineINTEL_8255X_ETHERNET_DEVICE(device_id,ich){\PCI_VENDOR_ID_INTEL,device_id,PCI_ANY_ID,PCI_ANY_ID,\PCI_CLASS_NETWORK_ETHERNET8,0xFFFF00,ich}staticstructpci_device_ide100_id_table[]={INTEL_8255X_ETHERNET_DEVICE(0x1029,0),INTEL_8255X_ETHERNET_DEVICE(0x1030,0),...}我们在”注册一个PCI网络设备“一节中知道,设备驱动需要向内核注册一个pci_device_id的结构,里面列出了驱动所能支持的设备列表。在这个例子中,它就是e100_id_table,这是e100.c驱动所支持的设备列表。需要注意的是:�第一个域(结构中的vendor参数)是固定的值PCI_VENDOR_ID_INTEL,它是intel分配的厂商id。[*]在列表。�第三和第四个域(subvendor和subdevice)被初始化为PCI_ANY_ID,这是因为前两个域(vendor和device)已经足够标识一个设备�大多数驱动都会用__devinitdata宏把设备列表标记为初始化数据,但是e100_id_table并没有这么做。在第七章中你会看到这个宏是如何使用的。驱动模块的初始化函数是e100_init_module,它与module_init类似。这个函数在系统启动时或者是模块加载时调用,它会调用pci_module_init注册设备驱动,同时也会注册驱动所能处理的网卡。具体细节参见”完整流程“一节。下面的快照显示了e100驱动与PCI相关的接口:NAMEe100staticint__devinite100_probe(structpci_dev*pdev,conststructpci_device_id*ent){...}staticvoid__devexite100_remove(structpci_dev*pdev){...}#ifdefCONFIG_PMstaticinte100_suspend(structpci_dev*pdev,u32state){...}staticinte100_resume(structpci_dev*pdev){...}#endifstaticstructpci_drivere100_driver={.name=DRV_NAME,.id_table=e100_id_table,.probe=e100_probe,.remove=__devexit_p(e100_remove),#ifdefCONFIG_PM.suspend=e100_suspend,.resume=e100_resume,#endif};staticint__inite100_init_module(void){...returnpci_module_init(&e100_driver);}staticvoid__exite100_cleanup_module(void){pci_unregister_driver(&e100_driver);}module_init(e100_init_module);module_exit(e100_cleanup_modu
本文标题:第六章:PCI子系统与网卡由于PCI设备在x86架构以
链接地址:https://www.777doc.com/doc-1314759 .html