通过玩具内核访问pci空间

时间:2014-11-07 19:43:43

标签: linux operating-system x86 kernel x86-64

我正在x86_64平台上编写一个玩具内核。我计划提升我的内核以使用VGA和其他设备。直到这一点,我正在使用键盘和UART连接我的玩具内核。现在我认为它已经足够成熟,可以开始使用一些基本设备。

但是,我发现很难在早期启动时找到有关PCI接口的文档。我正在寻找有关Linux PCI驱动程序的文档,但不是我真正需要的东西。我必须自己走路。如果BIOS将PCI空间映射到某个地方,我仍然感到困惑。

有人可以对此有所了解或指出我的一些文件。

由于

1 个答案:

答案 0 :(得分:0)

您要做的第一件事是执行PCI枚举。基本上有两种方法可以进行PCI枚举,基于端口或基于内存映射。基于端口的是旧方式,PCI Express引入了内存映射PCI配置空间。基于端口的方法仍适用于较新的计算机,也可与仿真器/虚拟机配合使用,以实现向后兼容。因此,我将在其余的解释中使用这种方法。这也有点复杂,如果您了解如何进行基于端口的枚举,则内存映射枚举非常简单。我应该补充说,一些非PC兼容的x86主板不支持基于端口的PCI枚举。

要通过端口访问PCI配置空间(以便执行枚举),您需要了解两个特定端口:

  1. CONFIG_ADDRESS - 端口0xCF8

  2. CONFIG_DATA - 端口0xCFC

  3. 基本上,您所做的是为CONFIG_ADDRESS写一个地址,并使用CONFIG_DATA读取或写入该地址的值。写入CONFIG_ADDRESS的地址格式为:

    31          30 - 24     23 - 16     15 - 11         10 - 8          7 - 2           1 - 0
    Enable Bit  Reserved    Bus Number  Device Number   Function Number Register Number 00
    

    假设您要使用CONFIG_DATA读取或写入值,则启用位基本上应始终设置为1。

    所以说你希望阅读配置中第一个设备的第一个寄存器,你会做类似的事情:

    outl(CONFIG_ADDRESS, 1 << 31);
    uint32_t result = inl(CONFIG_DATA)
    

    要确定每个寄存器的内容,您可以查看此表:

    PCI Configuration Space Table

    因此,在前面的示例中,第一个寄存器包含设备ID和供应商ID。

    现在您知道如何从PCI配置空间中读取您在找到设备时需要知道的寄存器。基本上,如果第一个寄存器是0xFFFFFFFF,那么没有设备连接到该设备号的总线。蛮力方式是扫描所有总线和设备,然后在找到设备时扫描功能。更复杂的方法将查看第一条总线上第一个设备的标头类型,以确定需要检查哪些总线。您可以找到有关如何执行更复杂枚举的更多信息here

    要执行基于内存映射的方法,您需要找到ACPI中指定的MCFG表(也包含在更简单的SFI中)。这将为您提供可用于PCI枚举的物理地址。所有内存地址都是该地址的偏移量。另外一个关键的区别是配置空间已经扩展,因此地址位字段与基于端口的枚举位字段不匹配。 PCI Express的地址使用以下格式:

    31 - 28   27 - 20     19 - 15        14 - 12          11 - 8                    7 - 2            1 - 0
    ZEROS     bus number  device number  function number  extended register number  register number  offset
    

    这是从MCFG表获得的物理寻址的偏移量。您可以找到有关PCI express here的更多信息。

    最后,虽然它是特定于设备的,但您可以从PCI配置空间获得的最有用信息通常是基址寄存器。这些通常是设备内存映射的物理地址,假设它是内存映射的,或者是假设它是基于端口的端口地址。当然,中断引脚和线也很有用。