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


前言

内核中的PCI子系统(也称PCI层)提供各种PCI设备驱动程序共同的所有通用功能。这个子系统让程序员减少了很多必须对各种设备所做的事,让驱动程序能以更简明的方式编写,使内核更易于收集和维护有关各种设备的信息,如描述信息和统计数据。

涉及的数据结构

在此列出的PCI层使用的一些关键数据结构类型。还有很多其他类型,但是下列几种是必须知道的。第一个结构定义在include/linux/mod_devicetale.h中,而另外两个定义在include/linux/pci.h

  • pci_device_id

    • 设备标识符。这不是Linux所使用的本地ID,而是根据PCI标准所定义的ID。后面一节会说明此ID的定义
  • pci_dev

    • 每个PCI设备都会被分派一个pci_dev实例,如同网络设备都会被分派net_device实例一样。这个结构由内核使用,已引用一个PCI设备。
  • pci_driver

    • 定义PCI层和设备驱动程序之间的接口。这个结构主要由函数指针组成。所有PCI设备都会使用这个结构。后面一节会进行介绍。

PCI设备驱动程序由pci_driver结构的实例定义。以下是其主要字段说明,特别注意NIC设备的情况。函数指针会由驱动程序初始化为该驱动程序内适当的函数。

  • char *name

    • 驱动程序的名字
  • const struct pci_device_id *id_table

    • 这是一个ID向量,内核用于把一些设备关联到此驱动程序,”PCI NIC“驱动程序注册范例一节会展示一个实例。
  • int (*probe)(struct pci_dev *dev, const pci_device_id *id)

    • 当PCI层发现它正在搜寻驱动程序的设备ID与前面所提到的id_table匹配时,就会调用该函数。此函数应该开启硬件,分配net_device结构,初始化并注册新设备。此函数中,驱动程序会分配正确工作所需的所有数据结构(如传输或接收时所用的缓冲区)。
  • void (*remove)(struct pci_dev *dev)

    • 当驱动程序从内核除名时,或者当个可热插拔设备被删除时,PCI层就会调用此函数。此函数为probe函数配对函数,用于清理任何数据结构和状态。网络设备使用此函数来释放已分配的I/O端口和I/O内存,为设备除名,释放net_device数据结构以及其他由设备驱动程序在probe函数内所分配的辅助数据结构。
  • int (*suspend)(struct pci_dev *dev, pm_message_t state)

  • int (*resume)(struct pci_dev *dev)

    • 当系统进入挂起模式以及重新继续时,PCI层就会调用这些函数。后面一节介绍
  • int (*enable_wake)(struct pci_dev *dev, u32 state, int enable)

    • 利用这个函数,驱动程序可以通过产生特定的电源管理事件信号,开启或关闭设备唤醒系统的能力。
  • struct pci_dynids dynids

    • 动态ID

PCI NIC设备驱动程序的注册

PCI设备独一无二识别方式是通过一些参数的组合,包括开发商以及模型等。这些参数由内核存储在pci_device_id类型的数据结构中,定义如下:

struct pci_device_id{
    unsigned int vendor, device;
    unsigned int subvendor, suddevice;
    unsigned int class, class_mask;
    unsigned long driver_data;
}

vendor和device通常就足以识别设备。subvendor和subdevice很少用到,通常设置为通配符(PCI_ANY_ID)。class和class_mask代表该设备所属的类,而NETWORK就是我们所要讨论的设备所属类。driver_data不是PCI ID的一部分,而是由驱动程序所使用的一个私有参数。

PCI设备驱动程序分别用pci_register_driver和pci_unregister_driver向内核注册和除名。这些函数定义在include/pci/pci.c中。除此之外,还有一个pci_module_init,是pci_register_driver的别名。因此pci_register_driver之前,旧版本内核中所用的函数名为pci_module_init,有些驱动程序依然使用它。

pci_register_driver需要一个pci_driver数据结构作为自变量。借助于pci_driverid_table向量,内核知道该驱动程序可以处理那些设备,此外,也因为pci_driver中所有虚拟函数,使内核有一种机制,可以与此驱动程序的任何相关联的设备彼此交互。

PCI的优点之一是,其之处寻找IRQ和每个设备所需要的其他资源的探测方式相当优雅。模块可以加载期间接收一些输入参数,以告知该如何配置其所负责的所有设备。但是,有些时候让驱动程序自行检查系统上的设备,然后为其负责的那些设备做配置会比较简答一点。必要时,用户依然可以退回到手动配置。

/sys文件系统输出有关系统总线(PCI,USB等等)的信息,包括各种设备及各种设备之间的关系。/sys也允许管理员为特定的设备驱动程序定义新的ID,使得除了驱动程序通过其pci_driver结构的id_table向量注册的静态ID之外,内核还能使用由用户所配置的参数。

这里不会说明内核根据设备ID查询驱动程序所采用的探测机制。然而值得一提的是,探测方式有两种:

  • 静态

    • 给定一个设备PCI ID,内核就能根据is_table向量查询出正确的PCI驱动程序(也就是pci_driver实例_。)这称为静态探测方式
  • 动态

    • 这种查询是根据用户手动配置ID,这种情况在实际中很少见,但是,在调试的情况下偶尔也使用。动态指的是系统管理员可以新增ID的能力,而不是指ID本身可自行变动。

    • 由于动态ID是在运行中的系统上配置的,只有当内核被编译成支持热插拔才能使用。

电源管理和网络唤醒

PCI电源管理事件由pci_driver数据结构的sudpendresume函数处理。除了分别负责PCI状态的保存和恢复之外,这些函数遇到NIC的情况时还需要采取特殊步骤:

  • suspend主要停止设备出口队列,使得该设备无法再传输。

  • resume重启出口队列,使得该设备得以再次传输。

网络唤醒(Wake-on-lan,WOL)是一种功能,允许NIC在接收到一种特殊类型的帧时唤醒处于待命模式的系统。WOL通常默认是关闭的。此功能用pci_enable_wake打开或关上。

当首次引入WOL功能时,只有一种帧可以唤醒系统:“魔术封包”这类特殊帧有两个主要特征:

  • 目的MAC地址属于正在接收的NIC(无论该地址是单播、多播还是广播)。

  • 帧中的某处(任何地方)会设置一段48位序列,后面再接NIC MAC地址,在一行中至少连续重复16此。

现在也有可能允许其他类型的帧唤醒系统。有少量设备可以根据在模块加载期间设置的一个参数开启或关闭WOL功能。

主要一个开启WOL功能的功能识别一个帧,其类型为可唤醒系统,就会产生一个电源管理通知信息,去做相应的工作。

PCI NIC驱动程序注册范例

drivers/net/e100.c中的Intel PRO/100 Ethernet驱动程序来说明驱动程序的注册:

#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},
    ....     
}

PCI NIC设备驱动程序会把一个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没有。

此模块由module_init宏指定的e100_init_module初始化。当此函数在引导期间或者模块加载期间由内核执行时,就会调用pci_moudule_init。此函数就会注册该驱动程序,并间接注册所有相关的NIC,后面一节进行介绍。

下面是e100驱动程序与PCI层接口相关的关键部分:

#define NAME "e100"
static int __devinit e100_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
    ...
}
static void __devexit e100_remove(struct pci_dev *pdev)
{
    ...
}
#ifdef CONFIG_PM
static int e100_suspend(struct pci_dev *pdev, u32 state)
{
    ...
}
static int e100_resume(struct pci_dev *pdev)
{
    ...
}
#endif

static struct pci_driver e100_driver = {
    .name = NAME,
    .id_table = e100_id_table,
    .probe = e100_probe,
    .remove = __devexit_p(e100_remove),
#ifdef CONFIG_PM
    .suspend = e100_suspend,
    .resume = e100_resume,
#endif
};

static int __init e100_init_module(void)
{
    ...
    return pci_module_init(&e100_driver);
}
static void __exit e100_cleanup_module(void)
{
    pci_unregister_driver(&e100_driver);
}
module_init(e100_init_module);
module_exit(e100_cleanup_module);

此外,注意:

  • 只有当内核支持电源管理时,才会对suspend和resume做初始化,所以只有当该条件为真时,e100_suspend和e100_resume这两个函数才会被包括在映像中。

  • pci_driver的remove字段用宏__devexit_p标记,而e100_remove用__devexit标记。

  • e100_probe用__devinit标记。

大蓝图

我们把前几节所了解的综合来看,采用PCI总线的系统以及一些PCI设备在引导期间会发生什么事。

当系统引导时,会建立一种数据库,把每个总线都关联一份已侦测到而使用该总线的设备列表。例如,PCI总线的描述符除了其他参数外,还包括一个已侦测PCI设备的列表。如“PCI NIC设备驱动程序的注册”一节所见,每个pci设备都可由pci_device_id结构中的一个大的字段集合唯一地识别,不过通常只需要几个字符就足够了。我们也知道PCI设备驱动程序如何定义一个pci_driver实例,以及如何用pci_register_driver与PCI层注册。设备驱动程序加载时,内核已建好其数据库,我们以配有三个PCI设备的图为例,说明当设备驱动程序A和B加载时会发生什么事。

在这里插入图片描述

当设备驱动程序A被加载时,会调用pci_register_driver并提供pci_driver实例而与PCI层注册。pci_driver结构有内含一个此驱动程序能驱动的PCI设备ID的向量。接着,PCI层使用该表去查看已侦测的PCI设备列表中与那些设备匹配。于是,就会建立该驱动程序的设备列表(如上图b所示)。此外,对每个匹配的设备而言,PCI层会调用相匹配的驱动程序中的pci_driver结构中锁提供的probe函数。probe函数会建立并注册相关联的网络设备。就此而言,设备Dev3就会被分派给驱动程序B。图c所示就是加载此去程程序后的结果。

当驱动程序于稍后卸载时,该模块的module_exit函数就会调用pci_unregister_driver。接着,由于其数据库,使得PCI层能够遍历所有与该驱动程序相关联的设备,并启用该驱动程序的remove函数。此函数就会将此网络设备除名。

通过/proc文件系统调整

/proc/pci文件可用于倾卸有关已注册的PCI设备的信息。pciutils套件中的lspci命令也可用于打印出有关于本地PCI设备的有用信息,但其信息取自/sys

本章涉及的函数和变量

名称描述
函数和宏
pci_register_driver注册PCI驱动程序
pci_unregister_driver除名PCI驱动程序
pci_module_init初始化PCI驱动程序
数据结构
struct pci_driver定义PCI驱动程序(多数都是虚拟回调函数)
struct pci_device_id存储PCI设备相关联的通用ID
struct pci_dev结构代表内核空间中的PCI设备

涉及的文件和目录

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jacky~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值