您的位置:首页 > 运维架构 > Linux

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

2015-02-05 14:17 330 查看
原文链接:http://www.cnblogs.com/Windeal/p/4284596.html

概述

    内核的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。


 










转载于:https://www.cnblogs.com/Windeal/p/4284596.html

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: