前兩天,我們介紹了作業系統如何透過 Memory-Mapped I/O (MMIO) 將外部設備的記憶體空間映射到物理記憶體空間,或者透過 Port-Mapped I/O (PMIO) 的方式,使用 I/O Port 與外部設備溝通。接下來,我們要來了解這些映射關係是如何設置的。
在 PCI 標準中,訪問 PCI 設備基本資料及設定的方式稱為 PCI Compatible Configuration Access Mechanism (CAM)。每個設備都有一些暫存器(Registers)來保存設備的基本資訊,例如 Device ID、Vendor ID 等,並提供設置暫存器來對該 PCI 設備進行設置。這些暫存器會排列成一個有序的結構,稱為 Configuration Space Header。
PCI 標準定義了這些暫存器的排列方式及結構外觀。Configuration Space Header 分為兩種類型:Type 0 和 Type 1。Type 0 用於一般外部設備,Type 1 則用於 Root Complex 等 PCI 架構元件。在此,我們僅探討 Type 0 的結構。
透過這個結構,作業系統可以讀取設備的 Device ID、Vendor ID 和 Class Code 等基本資訊。
PCIe 提供的 Configuration Space Header 增加了更多暫存器。然而,為了保持與 PCI 架構的兼容性,PCIe 的 Configuration Space Header 前部的暫存器仍與 PCI 保持一致。PCIe 在此基礎上進行了額外擴展。
處理器並沒有直接存取 PCI 設備 Configuration Space 的指令。作業系統必須透過 I/O Space 或 Physical Memory Address 與 PCI 設備進行通訊。這裡我們引入另一個新的地址空間——Configuration Space。所有 PCI 設備的 Configuration Space Header 會映射到這個空間中。
在 I/O Space 中,有兩個特殊的 Port:0xCF8 和 0xCFC。當作業系統要讀寫某個設備的 Configuration Space Header 時,會透過修改 0xCF8(CONFIG_ADDRESS)指向 Configuration Space 中對應的地址,然後使用 0xCFC(CONFIG_DATA)進行讀寫。
Configuration Address Space 並不是隨機排序的地址,而是使用 BDF(Bus, Device, Function)來定位設備。
根據 PCI Local Bus Specification,CONFIG_ADDRESS 的格式是透過 BDF 定位設備功能,並且位元 7:0 表示在 configuration space 中的 offset。bit 1 和 bit 0 總是為 0,每次讀取一個 DWORD(4 bytes)。
// arch/x86/pci/direct.c
static int pci_conf1_read(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 *value)
{
...
outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8);
switch (len) {
case 1:
*value = inb(0xCFC + (reg & 3));
break;
case 2:
*value = inw(0xCFC + (reg & 2));
break;
case 4:
*value = inl(0xCFC);
break;
...
}
static int pci_conf1_write(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 value)
{
...
outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8);
switch (len) {
case 1:
outb((u8)value, 0xCFC + (reg & 3));
break;
case 2:
outw((u16)value, 0xCFC + (reg & 2));
break;
case 4:
outl((u32)value, 0xCFC);
break;
}
...
}
Linux Kerenl 利用 PCI_CONF1_ADDRESS
計算地址,並透過 CF8 和 CFC 暫存器進行讀寫。
#define PCI_CONF1_ADDRESS(bus, devfn, reg) \
(0x80000000 | ((reg & 0xF00) << 16) | (bus << 16) \
| (devfn << 8) | (reg & 0xFC))
PCI_CONF1_ADDRESS
依據 PCI 規範進行地址計算,將 reg & 0xFC
用來截斷末兩位,並把超過索引範圍的高位 (reg & 0xF00) << 16
寫到 30:24 這些 reserve 的高位,不過這個部分的功能不太清楚。
由於 PCIe 架構擴展了 Configuration Space Header 的內容,額外的部分無法再透過 CAM 機制讀寫。因此,PCIe 額外提供了 PCI Express Enhanced Configuration Access Mechanism (ECAM)。ECAM 機制簡單來說,就是將 PCIe 設備的 Configuration Space Header 映射到 Physical Address Space 中,讓作業系統能夠像操作記憶體一樣,直接管理 PCIe 設備。
今天,我們介紹了 PCI 和 PCIe 設備的 Configuration Space 概念與存取方式。接下來,我們將進一步探討 MMIO 和 PMIO 的建立過程。