接前一篇文章:《PCI Express体系结构导读》随记 —— 第I篇 第2章 PCI总线的桥与配置(14)
2.3.1 PCI桥
在PCI Agent设备的配置空间中包含了许多寄存器,这些寄存器决定了该设备在PCI总线中的使用方法,本节不会全部介绍这些寄存器,因为系统软件只对部分配置寄存器感兴趣。PCI Agent设备使用的配置空间如图2-9所示:
本回继续讲解在PCI Agent设备的配置空间中包含的其余寄存器:
(10)Base Address Register 0~5寄存器
该寄存器简称为BAR寄存器,BAR寄存器保存PCI设备使用的地址空间的基地址,该基地址保存的是该设备在PCI总线域中的地址。其中每一个设备最多可以有6个基地址空间,但多数设备不会使用这么多组地址空间。
在PCI设备复位之后,该寄存器将存放PCI设备需要使用的基址空间大小、这段空间是I/O空间还是存储器空间、如果是存储器空间,该空间是否可预取。
系统软件对PCI总线进行配置时,首先获得BAR寄存器中的初始化信息,之后根据处理器系统的配置,将合理的基地址写入相应的BAR寄存器中。系统软件还可以使用该寄存器获得PCI设备使用的BAR空间的长度,其方法是向BAR寄存器写入0xFFFFFFFFF,之后再读取该寄存器。Linux系统使用__pci_read_base函数获得BAR寄存器的长度。
处理器访问PCI设备的BAR空间时,需要使用BAR寄存器提供的基地址。值得注意的是,处理器使用存储器域的地址,而BAR寄存器存放PCI总线域的地址。因此,处理器系统并不能直接使用“BAR寄存器 +?偏移”的方式访问PCI设备的寄存器空间,而需要将PCI总线域的地址转换为存储器域的地址。在Linux系统中,一个处理器系统使用BAR空间的正确方式如源代码2-2所示:
源代码2-2 Linux系统使用BAR空间的正确方法
pciaddr = pci_resource_start(pdev, 1);
if (!pciaddr) {
rc = -EIO;
dev_err(&pdev->dev, "no MMIO resource\n");
goto err_out_res;
}
……
regs = ioremap(pciaddr, CP_REGS_SIZE);
在Linux系统中,使用pci_dev->resource[bar].start参数保存BAR寄存器在存储器域的地址。在编写Linux设备驱动程序时,必须使用pci_resource_start函数获得BAR空间对应的存储器域的物理地址,而不能使用从BAR寄存器中读出的地址。
当驱动程序获得BAR空间在存储器域的物理地址后,再使用ioremap函数将这个物理地址转换为虚拟地址。Linux系统直接使用BAR空间的方法是不正确的,如源代码2-3所示:
源代码2-3 Linux系统使用BAR空间的错误方法
ret = pci_read_config_dword(pdev, 1, &pciaddr);
if (!pciaddr) {
rc = -EIO;
dev_err(&pdev->dev, "no MMIO resource\n");
goto err_out_res;
}
……
regs = ioremap(pciaddr, BAR_SIZE);
在Linux系统中,使用pci_read_config_dword函数获得的是PCI总线域的物理地址。在许多处理器系统(如Alpha和PowerPC处理器系统)中,PCI总线域的物理地址与存储器域的物理地址并不相等。
如果x86处理器系统使能了IOMMU,这两个地址也并不一定相等。因此处理器系统直接使用这个PCI总线域的物理地址,并不能确保访问PCI设备的BAR空间的正确性。除此之外,在Linux系统中,ioremap函数的输入参数为存储器域的物理地址,而不能使用PCI总线域的物理地址。
而在pci_dev->resource[bar].start参数中保存的地址已经经过PCI总线域到存储器域的地址转换,因此在编写Linux系统的设备驱动程序时,需要使用pci_dev->resource[bar].start参数中的物理地址,再用ioremap函数将物理地址转换为“存储器域”的虚拟地址。
PCI Agent设备配置空间中包含的其余寄存器请看下回。