pci_enable_device
一、注释
static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags)
{struct pci_dev *bridge;int err;int i, bars = 0;/** 此时电源状态可能是未知的,可能是由于新启动或者设备移除调用。* 因此获取当前的电源状态,这样像 MSI 消息写入这样的操作会按预期行为* (比如,如果设备在 enable 时确实位于 D0 状态)。*/if (dev->pm_cap) {u16 pmcsr;pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);}// 如果设备已经启用,只增加引用计数,然后直接返回if (atomic_inc_return(&dev->enable_cnt) > 1)return 0; /* 已经启用 */// 查找上游桥设备,如果存在则启用它bridge = pci_upstream_bridge(dev);if (bridge)pci_enable_bridge(bridge);// 只跳过和 SR-IOV 相关的资源for (i = 0; i <= PCI_ROM_RESOURCE; i++)if (dev->resource[i].flags & flags)bars |= (1 << i);for (i = PCI_BRIDGE_RESOURCES; i < DEVICE_COUNT_RESOURCE; i++)if (dev->resource[i].flags & flags)bars |= (1 << i);// 实际启用 PCI 设备err = do_pci_enable_device(dev, bars);if (err < 0)atomic_dec(&dev->enable_cnt); // 如果启用失败,则减少引用计数return err;
}/*** pci_enable_device - 在驱动程序使用设备前初始化设备。* @dev: 需要初始化的 PCI 设备** 在驱动程序使用设备前初始化设备。要求底层代码去启用 I/O 和内存。* 如果设备已经进入休眠,则将其唤醒。注意,该函数可能会失败。** 注意如果我们反复调用这个函数,我们不会真正多次启用设备(我们只是增加计数)。*/
int pci_enable_device(struct pci_dev *dev)
{return pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO);
}
EXPORT_SYMBOL(pci_enable_device);
在这段代码中,`pci_enable_device_flags` 是一个静态函数,它接受两个参数:一个指向 PCI 设备的指针和一个标志集合。函数的目的是启用指定的 PCI 设备,但首先会检查设备的电源管理(PM)能力和当前的电源状态,并递增设备的启用计数。如果设备已经启用,函数会立即返回。然后函数检查是否有上游桥设备,若存在则递归地启用桥设备。接下来,它会确定需要启用的资源并最终调用 do_pci_enable_device
函数。
pci_enable_device
函数封装了 pci_enable_device_flags
函数,用于启用 PCI 设备的 I/O 和内存资源。该函数导出了符号 pci_enable_device
,使得其他内核模块可以通过符号名引用该函数。
二、讲解
这段代码是Linux内核中用于启动PCI设备的一部分。下面将对这段代码的关键部分进行讲解。
static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags)
这是一个静态函数,其目的是用给定的标志(`flags`)来启动一个PCI设备(`dev`)。
struct pci_dev *bridge;int err;int i, bars = 0;
定义了指向PCI设备的桥接器的指针`bridge`,一个整型错误码`err`以及两个整型变量`i`和`bars`,其中`bars`用于跟踪需要启用的设备资源条目(BARs)。
if (dev->pm_cap) {
检查设备(dev
)是否支持电源管理(PM)功能。
u16 pmcsr;pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);}
如果支持,就从设备的配置空间中读取电源管理控制状态寄存器(PMCSR),并更新设备当前的电源状态。
if (atomic_inc_return(&dev->enable_cnt) > 1)return 0; /* already enabled */
使用原子操作增加设备使能计数器`enable_cnt`,如果这不是第一次使能(计数大于1),则表示设备已经使能,函数直接返回0。
bridge = pci_upstream_bridge(dev);if (bridge)pci_enable_bridge(bridge);
找到设备的上游桥接器(bridge
),如果存在,就启用这个桥接器。
for (i = 0; i <= PCI_ROM_RESOURCE; i++)...for (i = PCI_BRIDGE_RESOURCES; i < DEVICE_COUNT_RESOURCE; i++)...
这两个循环检查设备的所有资源条目,确定哪些BAR需要被启用,这基于给定的标志和资源的类型。
err = do_pci_enable_device(dev, bars);if (err < 0)atomic_dec(&dev->enable_cnt);return err;
}
调用`do_pci_enable_device`来实际启用这些资源。如果有错误发生,减少使能计数器并返回错误码。
int pci_enable_device(struct pci_dev *dev)
{return pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO);
}
EXPORT_SYMBOL(pci_enable_device);
这是一个外部可见的函数,用默认的标志(内存和I/O资源)来启用一个PCI设备。
整体而言,这段代码是驱动程序在使用PCI设备之前,对设备进行初始化和启用操作的过程。它确保设备可以响应内存和I/O操作,以及从低功耗状态中唤醒设备。
本函数在drivers\pci\pci.c中定义。
_PCI_NOP宏
一、注释
/** 如果系统没有PCI,很显然这些函数会返回错误。定义这些函数为简单的内联函数,* 以避免在驱动程序中的复杂操作。*/
#define _PCI_NOP(o, s, t) \static inline int pci_##o##_config_##s(struct pci_dev *dev, \int where, t val) \{ return PCIBIOS_FUNC_NOT_SUPPORTED; } // 定义宏_PCI_NOP来构造内联函数,若PCI操作不被支持,则返回错误码#define _PCI_NOP_ALL(o, x) _PCI_NOP(o, byte, u8 x) \ // 利用_PCI_NOP宏定义读写byte、word、dword三种操作_PCI_NOP(o, word, u16 x) \_PCI_NOP(o, dword, u32 x)_PCI_NOP_ALL(read, *) // 定义所有的read操作,如果系统无法执行PCI读取操作,它们将返回错误
_PCI_NOP_ALL(write,) // 定义所有的write操作,如果系统无法执行PCI写入操作,它们将返回错误
二、讲解
这段代码是用于操作PCI配置空间的宏定义和内联函数,适用于没有PCI支持的系统环境。在这种情况下,对PCI配置空间的读写操作将返回一个错误码,通常是因为PCI功能不被支持(`PCIBIOS_FUNC_NOT_SUPPORTED`)。
代码解释:
1. _PCI_NOP 宏定义: 它用于声明一个静态的内联函数,这个函数对应于PCI读或写操作,并返回一个错误码 PCIBIOS_FUNC_NOT_SUPPORTED。这个宏接收三个参数:操作类型 (o)、数据大小 (s) 和数据类型 (t)。具体来说:
- o 表示操作类型,可以是 read 或 write。
- s 表示数据的大小,可以是 byte(8位),`word`(16位)或 dword(32位)。
- t 表示对应的数据类型,分别是 u8(unsigned 8-bit)、`u16` (unsigned 16-bit)、`u32` (unsigned 32-bit).
2. _PCI_NOP_ALL 宏定义: 它是一个帮助宏,用于针对读和写操作声明所有可能的大小的内联函数,即字节、字和双字。
3. _PCI_NOP_ALL(read, *) 和 _PCI_NOP_ALL(write,): 这两个宏分别为读和写操作创建了三个函数,即针对 byte、`word` 和 dword 大小的数据。对 read 操作的函数,传入变量 val 是一个指针(`*指明取地址),这是因为读操作需要一个指针来存放读到的值。对 write` 操作,`val` 是一个普通变量,因为写操作是将该值写入到PCI配置空间。
4. 函数 pci_##o##_config_##s: 这是一个构建函数名的宏,这里使用了宏的字符串化(##)来组合操作类型和数据大小,创建特定的函数名。例如,`pci_read_config_byte` 或 pci_write_config_dword。
总结一下,这段代码就是为不支持PCI操作的系统提供了一组空操作(no-operation, NOP)函数,以便在该系统的PCI驱动中使用,从而避免了在驱动代码中添加复杂的条件编译指令。当驱动尝试进行PCI配置空间的访问时,这些函数会直接返回一个“功能不支持”的错误码。