zoukankan      html  css  js  c++  java
  • 深入理解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
    ", DRV_DESCRIPTION, DRV_VERSION); 
            pr_info("%s
    ", 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
    "); 
            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。


     











  • 相关阅读:
    PAT 乙级 1012 数字分类 (20) C++版
    PAT 乙级 1026 程序运行时间(15) C++版
    PAT 乙级 1009 说反话 (20) C++版
    PAT 乙级 1042 字符统计(20) C++版
    PAT 乙级 1046 划拳(15) C++版
    PAT 乙级 1063 计算谱半径(20) C++版
    CentOS安装oracle12C
    linux端口详解
    yum提示字符编码错误
    securecrt中文乱码
  • 原文地址:https://www.cnblogs.com/Windeal/p/4284596.html
Copyright © 2011-2022 走看看