setpci 命令与内核 pci_enable_device 与 pci_disable_device 函数

lspci 与 setpci

lspci -nvv 查看到的部分信息:

01:00.0 0200: 1af4:1041 (rev 01)
       Subsystem: 1af4:1100
        Physical Slot: 0
        Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B- DisINTx+
        Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-

lspci 通过读取接口的 pci 配置空间获取源信息,然后进行解析并输出解析的结果。setpci 可以用来读取、修改接口 pci 配置空间的内容。

下面我使用 setpci 读取上述 lspci -nvv 输出访问的一些 pci 配置空间内容。

使用 setpci 读取 pci 配置空间

1. 查看 setpci 支持读取的 pci 配置空间寄存器

root@virt-debian10:/home/longyu# setpci --dumpregs | head -n 10
cap pos w name
     00 W VENDOR_ID
     02 W DEVICE_ID
     04 W COMMAND
     06 W STATUS
     08 B REVISION
     09 B CLASS_PROG
     0a W CLASS_DEVICE
     0c B CACHE_LINE_SIZE
     0d B LATENCY_TIMER

第一列表示偏移量,第二列表示数据的长度(W 表示一个字长即两个字节长度,B 表示一个字节长度),第三列表示数据字段的名称。例如 0x00 处的一个字为 vendor id 的值,0x02 处的一个字为 device id 的值,0x04 处则为 COMMAND 的值等等。

2. 读取相关字段配置空间的值

root@virt-debian10:/home/longyu# setpci -s 01:00.0 00.w
1af4
root@virt-debian10:/home/longyu# setpci -s 01:00.0 02.w
1041
root@virt-debian10:/home/longyu# setpci -s 01:00.0 04.w
0507
root@virt-debian10:/home/longyu# setpci -s 01:00.0 06.w
0010
root@virt-debian10:/home/longyu# setpci -s 01:00.0 08.b
01
root@virt-debian10:/home/longyu# setpci -s 01:00.0 09.b
00
root@virt-debian10:/home/longyu# setpci -s 01:00.0 0a.w
0200
root@virt-debian10:/home/longyu# setpci -s 01:00.0 0c.b
00
root@virt-debian10:/home/longyu# setpci -s 01:00.0 0d.b
00

上述命令行中,-s 指定访问的 pci 设备,xxx.[wb] 表示需要读取的配置空间偏移量与长度,可以看到读取的值与文首 lspci -nvv 的部分输出(vendor id、device id)一致。

使用 setpci 写入 pci 配置空间寄存器

这里我以写入 pci 配置空间 COMMAND 寄存器为例进行描述。

写入前的 pci 配置空间 command 寄存器信息:

	Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B- DisINTx+

执行写入命令:

root@virt-debian10:/home/longyu# setpci  -s 01:00.0 04.W=0

setpci 读取写入信息确认写入生效:

root@virt-debian10:/home/longyu# setpci -s 08:00.0 04.W
0000

重新查看 lspci 显示信息:

	Control: I/O- Mem- BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx-

能够看到写入生效了。注意不要写入一个【正在使用】的 pci 网卡的上述 COMMAND 配置空间寄存器,一旦写入会造成网卡无法正常工作。

pci_enable_device 与 pci_disable_device 对 pci 配置空间的设定

下文中的代码摘自 3.16.35 内核。

1. pci_enable_device

/**
 * pci_enable_device - Initialize device before it's used by a driver.
 * @dev: PCI device to be initialized
 *
 *  Initialize device before it's used by a driver. Ask low-level code
 *  to enable I/O and memory. Wake up the device if it was suspended.
 *  Beware, this function can fail.
 *
 *  Note we don't actually enable the device many times if we call
 *  this function repeatedly (we just increment the count).
 */
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

static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags)
{
        struct pci_dev *bridge;
        int err;
        int i, bars = 0;

        /*
         * Power state could be unknown at this point, either due to a fresh
         * boot or a device removal call.  So get the current power state
         * so that things like MSI message writing will behave as expected
         * (e.g. if the device really is in D0 at enable time).
         */
        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;               /* already enabled */

.........

        /* only skip sriov related */
        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);

        err = do_pci_enable_device(dev, bars);
        if (err < 0)
                atomic_dec(&dev->enable_cnt);

此函数中会使用每个设备的使能计数判定是否已经使能过,已经使能过则不再使能,直接返回。此函数核心在于调用 do_pci_enable_device 函数。

do_pci_enable_device 函数主要代码

static int do_pci_enable_device(struct pci_dev *dev, int bars)
{
        int err;
        struct pci_dev *bridge;
        u16 cmd;
        u8 pin;

        err = pci_set_power_state(dev, PCI_D0);
        if (err < 0 && err != -EIO)
                return err;

.........

        err = pcibios_enable_device(dev, bars);
        if (err < 0)
                return err;
        pci_fixup_device(pci_fixup_enable, dev);

        if (dev->msi_enabled || dev->msix_enabled)
                return 0;

        pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
        if (pin) {
                pci_read_config_word(dev, PCI_COMMAND, &cmd);
                if (cmd & PCI_COMMAND_INTX_DISABLE)
                        pci_write_config_word(dev, PCI_COMMAND,
                                              cmd & ~PCI_COMMAND_INTX_DISABLE);
        }

        return 0;
}

do_pci_enable_device 函数首先设定设备的电源状态为 PCI_D0 状态。然后首先调用 pcibios_enable_device 做一些【架构相关】的 pci 设备使能操作。

中间跳过了我不太熟悉的 pci 桥的配置逻辑。

pci_fixup_device 用于修复一些可恢复的错误。此后对于未使能 msi 与 misx 的 pci 设备,读取 pci 配置空间中的中断引脚。当设备绑定到一个中断引脚上时,读取 PCI 配置空间的控制字节 04 偏移处的一个字节,根据读取的结果判断 INTX 是否开启,未开启,则使能 INTX 中断。

pci_disable_device 函数

/**
 * pci_disable_device - Disable PCI device after use
 * @dev: PCI device to be disabled
 *
 * Signal to the system that the PCI device is not in use by the system
 * anymore.  This only involves disabling PCI bus-mastering, if active.
 *
 * Note we don't actually disable the device until all callers of
 * pci_enable_device() have called pci_disable_device().
 */
void pci_disable_device(struct pci_dev *dev)
{
        struct pci_devres *dr;

        dr = find_pci_dr(dev);
        if (dr)
                dr->enabled = 0;

        dev_WARN_ONCE(&dev->dev, atomic_read(&dev->enable_cnt) <= 0,
                      "disabling already-disabled device");

        if (atomic_dec_return(&dev->enable_cnt) != 0)
                return;

        do_pci_disable_device(dev);

        dev->is_busmaster = 0;
}
EXPORT_SYMBOL(pci_disable_device);

pci_disable_device 用于【禁用】一个 pci 设备,当设备已经被禁用时,会打印禁用一个已经禁用过的设备的 oops 告警信息。

此后读取使能标记,原子减 1 后不为 0 时此函数直接返回。正常流程继续调用 do_pci_disable_device 函数。

do_pci_disable_device 函数

static void do_pci_disable_device(struct pci_dev *dev)
{
        u16 pci_command;

        pci_read_config_word(dev, PCI_COMMAND, &pci_command);
        if (pci_command & PCI_COMMAND_MASTER) {
                pci_command &= ~PCI_COMMAND_MASTER;
                pci_write_config_word(dev, PCI_COMMAND, pci_command);
        }

        pcibios_disable_device(dev);
}

此函数中读取 pci 配置空间 04 偏移处两个字节大小的控制信息,清除 PCI_COMMAND_MASTER 标志后重新写回到配置空间中。最后调用 pcibios_disable_device 来执行一些架构相关的使能操作。

<think>好的,我现在需要帮助用户理解Linux内核中的pci_ioremap_bar函数的功能和用法。首先,我应该回顾一下已有的知识,看看已有的引用材料是否相关。用户提供的引用中提到了ioremap_nocache、devm_ioremap和devm_ioremap_resource,这些函数内存映射有关,特别是I/O内存的映射。 首先,用户的问题是关于pci_ioremap_bar的,这个函数可能和PCI设备的内存映射相关。根据已有的知识,ioremap_nocache用于将物理地址映射到内核虚拟地址空间,并且禁用缓存,这可能用于直接访问硬件寄存器。而devm_ioremap_resource则结合了资源申请和映射,确保资源在使用前已被正确请求。这提示我,pci_ioremap_bar可能也是类似的函数,但特定用于PCI设备的BAR(Base Address Register)。 接下来,我需要确认pci_ioremap_bar的具体功能。根据Linux内核文档,PCI设备的BAR存储了设备所需的内存或I/O空间的基地址。驱动程序需要将这些物理地址映射到内核的虚拟地址空间,以便访问设备的寄存器或内存。pci_ioremap_bar的作用应该就是完成这个映射,可能类似于ioremap_nocache,但专为PCI BAR设计。 然后,考虑用法。用户可能需要知道如何调用这个函数,参数是什么。通常,PCI设备结构体(pci_dev)和BAR的序号是参数,返回映射后的虚拟地址。例如,使用pci_ioremap_bar(pdev, bar_num)来获取对应BAR的虚拟地址。 还需要注意资源管理的问题,比如是否使用devm_版本来自动释放资源。用户提到的引用4中,devm_ioremap_resource是先申请资源再映射,而pci_ioremap_bar可能有对应的devm版本,比如devm_pci_ioremap_bar,这样在设备卸载时会自动释放映射,避免资源泄漏。 此外,示例代码会帮助用户理解如何在实际驱动中使用这个函数。比如在probe函数中,获取PCI设备后,调用pci_ioremap_bar来映射特定的BAR,然后通过指针访问该内存区域。 最后,要确保回答覆盖功能、用法、参数、注意事项和示例,并且引用相关材料。例如,引用3中的lspci输出显示了PCI设备的内存地址和I/O端口,这可能对应于某个BAR,而驱动程序需要使用pci_ioremap_bar来映射这些区域。</think>### pci_ioremap_bar 函数的功能用法 #### 一、功能解析 `pci_ioremap_bar` 是 Linux 内核中用于映射 **PCI 设备 BAR(Base Address Register)** 到内核虚拟地址空间的函数。BAR 存储了 PCI 设备需要的内存或 I/O 空间基地址,驱动通过该函数将物理地址转换为内核可访问的虚拟地址,从而操作硬件寄存器或设备内存。其特点包括: 1. **自动检测资源类型**:根据 BAR 属性(如内存类型、可预取性)选择是否禁用缓存[^1][^4]。 2. **简化映射流程**:封装了 PCI 配置空间读取和地址映射的逻辑。 #### 二、函数原型参数 ```c void __iomem *pci_ioremap_bar(struct pci_dev *pdev, int bar); ``` - **`pdev`**:指向 PCI 设备的结构体指针(`struct pci_dev`)。 - **`bar`**:BAR 的编号(0-5,对应 PCI 标准定义的 6 个 BAR)。 #### 三、使用步骤 1. **启用 PCI 设备资源**:调用 `pci_enable_device` 确保设备已激活。 2. **请求 BAR 所有权**:使用 `pci_request_region` 声明资源占用(防止冲突)。 3. **执行地址映射**:调用 `pci_ioremap_bar` 获取虚拟地址指针。 4. **访问硬件资源**:通过 `ioread32`/`iowrite32` 等函数操作内存。 5. **资源释放**:卸载驱动时反向释放资源(`pci_iounmap_bar` 和 `pci_release_region`)。 #### 四、代码示例 ```c #include <linux/pci.h> static int probe(struct pci_dev *pdev, const struct pci_device_id *id) { void __iomem *base; int ret; ret = pci_enable_device(pdev); if (ret) return ret; ret = pci_request_region(pdev, 0, "my_driver"); if (ret) goto fail_enable; base = pci_ioremap_bar(pdev, 0); if (!base) { ret = -ENOMEM; goto fail_request; } // 示例:读取寄存器值 u32 reg_value = ioread32(base + REG_OFFSET); return 0; fail_request: pci_release_region(pdev, 0); fail_enable: pci_disable_device(pdev); return ret; } ``` #### 五、注意事项 1. **资源管理**:推荐使用 `devm_pci_ioremap_bar` 自动释放资源。 2. **缓存策略**:若 BAR 标记为可预取(prefetchable),映射时可能启用缓存优化[^1][^3]。 3. **错误处理**:需检查返回值,避免空指针访问。 #### 六、关联场景 例如在 Intel 82540EM 网卡驱动中,通过 `lspci -v` 可看到其内存地址为 `f0200000`(32-bit, non-prefetchable),此时需使用 `pci_ioremap_bar` 禁用缓存以确保寄存器访问的原子性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值