深入理解Linux网络技术内幕——PCI层和网络接口卡

概述

    内核的PCI子系统(即PCI层)提供了不同设备一些通用的功能,以便简化各种设备驱动程序。
    PCI层重要结构体如下:
pci_device_id
    设备标识,根据PCI标志定义的ID,而不是Linux本地的。
pci_dev
    类似于网络设备的net_device。每个PCI会被分配一个net_dev实例。
pci_driver
    PCI层和设备驱动程序之间的接口。主要由一些函数指针组成。如下所示:
struct pci_driver {
    struct list_head node;                                                                                               
    char *name; //驱动程序名字
    const struct pci_device_id *id_table;   /* ID向量,内核用于把设备关联到此驱动程序 */
    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  (*suspend_late) (struct pci_dev *dev, pm_message_t state);
    int  (*resume_early) (struct pci_dev *dev);
    int  (*resume) (struct pci_dev *dev);                   /* Device woken up */
    void (*shutdown) (struct pci_dev *dev);
    struct pci_error_handlers *err_handler;
    struct device_driver    driver;
    struct pci_dynids dynids;
};



PCI NIC设备的注册

    PCI设备由 pci_device_id (的成员共同)唯一标识。
struct pci_device_id {
    __u32 vendor, device;       /* Vendor and device ID or PCI_ANY_ID*/ //通常 vendor, device就足以标识设备
    __u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID *///很少用到
    __u32 class, class_mask;    /* (class,subclass,prog-if) triplet *///设备所属的类,如network类
    kernel_ulong_t driver_data; /* Data private to the driver *///不属于PCI标识部分,而是驱动私有参数
};


    每一个设备驱动程序会注册一个pci_device_id 实例的向量(即一系列的pci_device_id 实例),这个向量包含了该驱动程序所能处理的设备的ID。
    下面是设备驱动程序的注册和删除的函数:
int __pci_register_driver(struct pci_driver *drv, struct module *owner, const char *mod_name)
void pci_unregister_driver(struct pci_driver *drv)
pci_module_init()//在一些驱动程序上作为__pci_register_driver别名

    内核根据设备ID查询设备的驱动程序,这是一种探测机制。探测机制有两种方式:静态和动态。
 静态:
    给定一个PCI的ID,内核根据该ID从id_table向量查询出对应的驱动程序
动态:
    根据用户手动配置的ID,比较少用到,要求内核编译时支持热插拔。

电源管理和网络唤醒

    PCI的电源管理事件有pci_driver的suspend和resume进行。这两个函数分别负责PCI状态的保存和恢复。如果遇到NIC的的情况还需要分别进行下面步骤:
    suspend:停止设备出口队列,使得该设备无法再传输:
    resume:重启出口队列,是设备可以继续传输。

    网络唤醒功能允许NIC在接收到某种特殊帧是唤醒系统。这个功能通常是被禁用的,但是此功能可以用pci_enable_wake打开或关上。关于这部分我发现linux-3.12.36里好像没有这个函数了,可能有使用了其它网络唤醒方法,留着以后再补充了。


    PCI NIC驱动程序注册范例

以Intel PRO/100 Ethernet驱动程序说明NIC设备驱动程序的注册,源文件为drivers/net/e100.c。

初始化pci_device_id内容:

#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 }
 
/****************************************************************************************/
#define DEFINE_PCI_DEVICE_TABLE(_table) \ 
    const struct pci_device_id _table[] __devinitconst
/***************************************************************************************/

static DEFINE_PCI_DEVICE_TABLE(e100_id_table) = { 
    INTEL_8255X_ETHERNET_DEVICE(0x1029, 0), 
    INTEL_8255X_ETHERNET_DEVICE(0x1030, 0), 
    INTEL_8255X_ETHERNET_DEVICE(0x1031, 3), 
    INTEL_8255X_ETHERNET_DEVICE(0x1032, 3), 
    INTEL_8255X_ETHERNET_DEVICE(0x1033, 3), 
    INTEL_8255X_ETHERNET_DEVICE(0x1034, 3), 
    INTEL_8255X_ETHERNET_DEVICE(0x1038, 3), 
    INTEL_8255X_ETHERNET_DEVICE(0x1039, 4), 
    INTEL_8255X_ETHERNET_DEVICE(0x103A, 4), 
    INTEL_8255X_ETHERNET_DEVICE(0x103B, 4), 
    INTEL_8255X_ETHERNET_DEVICE(0x103C, 4), 
    INTEL_8255X_ETHERNET_DEVICE(0x103D, 4), 
    INTEL_8255X_ETHERNET_DEVICE(0x103E, 4), 
    INTEL_8255X_ETHERNET_DEVICE(0x1050, 5), 
    INTEL_8255X_ETHERNET_DEVICE(0x1051, 5), 
    INTEL_8255X_ETHERNET_DEVICE(0x1052, 5), 
    INTEL_8255X_ETHERNET_DEVICE(0x1053, 5), 
    INTEL_8255X_ETHERNET_DEVICE(0x1054, 5), 
    INTEL_8255X_ETHERNET_DEVICE(0x1055, 5), 
    INTEL_8255X_ETHERNET_DEVICE(0x1056, 5), 
    INTEL_8255X_ETHERNET_DEVICE(0x1057, 5), 
    INTEL_8255X_ETHERNET_DEVICE(0x1059, 0), 
    INTEL_8255X_ETHERNET_DEVICE(0x1064, 6), 
    INTEL_8255X_ETHERNET_DEVICE(0x1065, 6), 
    INTEL_8255X_ETHERNET_DEVICE(0x1066, 6), 
    INTEL_8255X_ETHERNET_DEVICE(0x1067, 6), 
    INTEL_8255X_ETHERNET_DEVICE(0x1068, 6), 
    INTEL_8255X_ETHERNET_DEVICE(0x1069, 6), 
    INTEL_8255X_ETHERNET_DEVICE(0x106A, 6), 
    INTEL_8255X_ETHERNET_DEVICE(0x106B, 6), 
    INTEL_8255X_ETHERNET_DEVICE(0x1091, 7), 
    INTEL_8255X_ETHERNET_DEVICE(0x1092, 7), 
    INTEL_8255X_ETHERNET_DEVICE(0x1093, 7), 
    INTEL_8255X_ETHERNET_DEVICE(0x1094, 7), 
    INTEL_8255X_ETHERNET_DEVICE(0x1095, 7), 
    INTEL_8255X_ETHERNET_DEVICE(0x10fe, 7), 
    INTEL_8255X_ETHERNET_DEVICE(0x1209, 0), 
    INTEL_8255X_ETHERNET_DEVICE(0x1229, 0), 
    INTEL_8255X_ETHERNET_DEVICE(0x2449, 2), 
    INTEL_8255X_ETHERNET_DEVICE(0x2459, 2), 
    INTEL_8255X_ETHERNET_DEVICE(0x245D, 2), 
    INTEL_8255X_ETHERNET_DEVICE(0x27DC, 7), 
    { 0, } 
};


在模块的初始化和卸载接口中完成PCI设备驱动程序的注册和注销:

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 
    /* Power Management hooks */ 
    .suspend =      e100_suspend, 
    .resume =       e100_resume, 
#endif 
    .shutdown =     e100_shutdown, 
    .err_handler = &e100_err_handler, 
};
static int __init e100_init_module(void) 
{ 
    if (((1 << debug) - 1) & NETIF_MSG_DRV) { 
        pr_info("%s, %s\n", DRV_DESCRIPTION, DRV_VERSION); 
        pr_info("%s\n", DRV_COPYRIGHT); 
    } 
    return pci_register_driver(&e100_driver); 
}
static void __exit e100_cleanup_module(void) 
{ 
    pci_unregister_driver(&e100_driver); 
}
module_init(e100_init_module); 
module_exit(e100_cleanup_module);


其中的一些函数指针原型:

#define DRV_NAME        "e100"
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) 
            pr_err("Etherdev alloc failed, aborting\n"); 
        return -ENOMEM; 
    }
                         ……
                         ……
}


PCI子系统总览

(a)在系统引导时,会建立一个数据库,把每个总线都关联到一份已侦测到而使用该总线的设备列表。PCI总线的描述符处理其他参数外,还包括一个已侦测PCI设备的列表。

(b)当驱动程序被加载,调用pci_register_driver注册pci_driver到PCI层时,PCI会使用pci_driver结构中的PCI设备ID参数id_table与已侦测到的PCI设备列表匹配,若匹配到就会建立该驱动程序的设备列表。对于每个匹配到的设备,PCI层会调用相匹配的驱动程序中的pci_driver结构中的probe函数,建立并注册相关联的网络设备。

技术分享

技术分享

技术分享

/proc/pci文件包含了已注册的PCI设备的信息。pciutils套件中的lspci命令会输出有关本地PCI设备的信息,其中有些信息取自/sys。


 











郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。