1、基本概念
cpu位址空間和pci位址空間是兩個常用的比較容易混淆的概念,特别是其中不同系列的cpu的實作還各不相同:x86系列cpu位址空間和pci位址空間是重合的,即為同一空間;而非x86 cpu的cpu位址空間和pci位址空間為兩個獨立的空間。
也許是因為pci總線是intel發明的,是以x86内部總線和pci總線實作是一緻的,但非x86系列的cpu實作pci總線需要用到總線轉換。
1.1、非x86系列的CPU空間和PCI空間互訪(以PowerPC為例):

因為PCI位址空間和CPU位址空間是獨立的,是以PCI裝置和CPU之間的互訪需要用到位址轉換。涉及到兩個概念,inbound和outbound。
Inbound視窗:即将CPU的一段位址空間映射到PCI位址空間上,供PCI裝置通路CPU空間所用,即inbound視窗。因為對cpu來說pci通路就是inbound通路。
Outbound視窗:即将PCI的一段位址空間映射到CPU位址空間上,供CPU通路PCI空間所用,即Outbound視窗。因為對cpu來說通路pci空間就是outbound通路。
1.2、x86系列的CPU空間和PCI空間互訪:
對x86 cpu來說,就沒有了cpu位址空間和pci位址空間之分,這兩個空間是重合的。是以pci外設和cpu之間的互相通路就不需要設定位址轉換視窗:
Inbound通路:pci裝置通路cpu空間,這個不需要設定inbound視窗,對外設來說所有的位址空間都是可以通路的。
Outbound通路:cpu通路pci外設,這個也還需要保留一段cpu位址給外設,讓外設的視窗能映射到這段空間來,即MMIO空間。有點類似于Outbound視窗,但是不需要位址轉換。
1.3、x86系列的IO空間和CFG空間:
由于x86 cpu對pci标準的完美支援,是以x86 cpu還支援pci的io空間通路和pci的配置空間通路:
io空間:pci标準規定的io位址空間最大可以有4G,但是x86隻實作了64k的大小。io空間用來實作早期相容的外設寄存器的通路(IO端口),和用來映射pci外設的io空間。
配置空間:用來通路pci總線裝置的配置空間,而x86也把内部的寄存器組織成虛拟的pci裝置,使用通路pci配置寄存器的方式來通路内部寄存器。
通路配置空間的方法有兩種:一是通過CF8/CFC io端口的間接通路來通路配置空間;二是通過mmcfg方式,把配置空間映射到memory空間來通路。對每個配置空間來說,CF8/CFC方式隻能通路傳統的pci配置空間256位元組,而mmcfg通路方式,能通路pcie的整個配置空間4k。
2、x86 CPU位址空間配置設定
本節以系統的橋片為例,來說明x86系列cpu的位址空間配置設定。x86 其他系列cpu的位址空間配置設定類似。
x86 xeon系列cpu 在32位系統下面,通過PAE(Physical address Extension)機制可以通路到36位的位址,即最大64G的空間。
2.1、0-1M 相容空間:
0-FFFFF | 0-640k正常記憶體(MS-DOS Area) | 這一段區域就是ram。 其中有功能劃分的區域是:起始位置的1 KB被用做BIOS中斷向量表,随後的1 KB被用做BIOS資料區 | |
A0000-BFFFF | 640 – 768 kB Video Buffer Area | 1、這一段區域是顯示卡的顯示RAM區域,老式的VGA顯示模式直接往這段顯存寫資料,就可以顯示。現在估計隻有bios階段使用這種顯示方式,系統起來後會開啟更進階的顯示卡顯示模式。 2、被顯存位址覆寫的這一塊128K大小的記憶體,可以被利用起來當做SMM記憶體。SMM是CPU一種等級最高的管理模式,是以它的記憶體在正常下不可以被通路。 | 1、PCI在支援VGA顯示時,有個VGAEN功能,比較特殊,值得關注。VGA顯示卡裝置不需要配置正常的pci bar寄存器位址,而隻需要使能顯示卡所挂在PCI-PCI橋裝置的配置寄存器0x3E bit 3(VGA Enable),顯示卡就會響應專為VGA保留的固定pci memory位址(A0000-BFFFF)和pci io位址(03c0-03df)。 2、什麼是SMM模式? SMM是System Management Mode系統管理模式的縮寫。從Intel 386SL開始,此後的x86架構微處理器中都開始支援這個模式。在這個模式中,所有正常執行的軟體,包括作業系統都已經暫停運作。隻有特别的單獨軟體,具備高特權模式的軟體才能運作。通常這些軟體都是一些固件程式或者是硬體輔助調試器。 x86 處理器的模式Mode模式 起始支援的處理器 Real mode Intel 8086 Protected mode Intel 80286 Virtual 8086 mode Intel 80386 Unreal mode Intel 80386 System Management Mode Intel 386SL Long mode AMD Opteron |
C0000-CFFFF | 768 - 832 kB VGA Video BIOS ROM IDE Hard Disk BIOS ROM Optional Adapter ROM BIOS or RAM UMBs | 1、這一段區域存放顯示卡的Option Rom還有其他裝置的OptionRom(如硬碟、網卡..)。 | 這一段區域,是OptionRom和BIOS區域覆寫了原RAM區域。由于RAM的通路速度遠遠快于這些固件的通路速度,是以通常的做法是把固件中的内容拷貝到相同位址的RAM中,然後再使能RAM而屏蔽原有的固件映射。 通路BIOS和OptionRom内容和位址都沒有改變,但是速度卻加快了。這種做法就叫ROM Shadowing |
D0000-DFFFF | 832 - 896 kB Optional Adapter ROM BIOS or RAM UMBs | 這一段區域也是來存放裝置的OptionRom。如果沒有OptionRom覆寫,那就是正常記憶體 | |
E0000-EFFFF | 896 - 960 kB System BIOS Plug and Play Extended Information | 擴充BIOS區域。 | |
F0000-FFFFF | 960 kB–1 MB System BIOS ROM | 正常BIOS區域,映射到BIOS晶片。CPU的第一句指令0xFFFF0就跳到該區域 |
2.2、1M以上的memory位址空間:
1M-TOLM | 低于4G的正常記憶體 | 這一段的記憶體就是低于4G的可用的記憶體,其中有兩段區域比較特殊。 1、15 MB - 16 MB Window (ISA Hole)傳統的ISA黑洞,現在基本不支援。 2、Extended SMRAM Space (TSEG)擴充SMM記憶體。前面已經有VGA RAM覆寫的128k記憶體可做SMM記憶體使用,系統還允許配置設定更多的SMM記憶體。 | “Coherency Protocol”Intel 橋片通過同步協定保證所有對記憶體通路的一緻性。 |
HECBASE-(HECBASE+256M) | MMCFG(Memory Mapped Configuration) 映射到memory空間的pci配置寄存器 | 256M的計算方法: 256bus x 32device x 8function x 4k bytes register = 256M bytes | mmcfg、mmio這兩段區域覆寫的相同位址的正常記憶體比較大,怎麼樣能使用到這段被覆寫的記憶體? 晶片組和記憶體控制其有一種叫做Main Memory Reclaim AddressRange。通過這個技術可将重疊得部分的記憶體位址印射到4g以上的位址上去,這個功能是由硬體決定的。 |
(HECBASE+256M) - (4G-32M) | MMIO(Memory Mapped I/O) 可配置設定給外設使用的pci memory空間 | 為什麼要在4G以下設定Low MMIO區域,造成記憶體被分割成兩塊,為什麼不能把MMIO都放在4G以上?主要還是為了照顧pci 32/64bit的相容,32bit的pci位址需要放在4G以下的空間。 | |
(4G-32M) - 4G | CPU-spec | 4G以下的32M區域是CPU的一些特殊區域,主要由以下幾部分組成: 1、16M fireware位址; 2、一些直接通路的cpu寄存器; 3、Interrupt、I/O APIC區域; | |
4G - ram_size | 高于4G的正常記憶體 | Coherency Protocol”Intel 橋片通過同步協定保證所有對記憶體通路的一緻性。 | |
ram_size - | high MMIO | 在高端記憶體之上一直到CPU支援的最大空間,都可以用來映射64bit的pci memory外設位址 |
2.3、x86 io位址空間:
x86隻實作64k大小的io空間,其中低4k是相容的io空間用做專門用途,4k以上的io位址空間可以配置設定給外部裝置使用。
2.4、指令操作:
在linux下通過以下指令檢視cpu的位址空間配置設定:
- cat /proc/iomem 檢視cpu的pci memory空間配置設定
- cat /proc/ioports 檢視cpu的pci io空間配置設定
3、x86内部寄存器的通路
除了一些是需要直接在memory空間通路的寄存器。x86把大部分内部的寄存器組織成虛拟的pci裝置,使用通路pci配置寄存器的方式來通路内部寄存器。
通路配置空間的方法有兩種:
- 一是通過CF8/CFC io端口的間接通路來通路配置空間;
- 二是通過mmcfg方式,把配置空間映射到memory空間來通路。對每個配置空間來說,CF8/CFC方式隻能通路傳統的pci配置空間256位元組,而mmcfg通路方式,能通路pcie的整個配置空間4k。
linux核心态實作pci配置空間通路的函數有以下:
static inline int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val)
{
return pci_bus_read_config_byte (dev->bus, dev->devfn, where, val);
}
static inline int pci_read_config_word(struct pci_dev *dev, int where, u16 *val)
{
return pci_bus_read_config_word (dev->bus, dev->devfn, where, val);
}
static inline int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val)
{
return pci_bus_read_config_dword (dev->bus, dev->devfn, where, val);
}
static inline int pci_write_config_byte(struct pci_dev *dev, int where, u8 val)
{
return pci_bus_write_config_byte (dev->bus, dev->devfn, where, val);
}
static inline int pci_write_config_word(struct pci_dev *dev, int where, u16 val)
{
return pci_bus_write_config_word (dev->bus, dev->devfn, where, val);
}
static inline int pci_write_config_dword(struct pci_dev *dev, int where, u32 val)
{
return pci_bus_write_config_dword (dev->bus, dev->devfn, where, val);
}
這些函數的内部實作,會判斷cpu是否進行了mmcfg映射。如果已經進行了mmcfg映射,則使用mmcfg模式通路;如果沒有實作mmcfg,則使用CF8/CFC方式通路。
具體的實作過程如下:
- 1、這些函數的定義在driver/pci/access.c,可以看到其中的關鍵是bus->ops指針:
#define PCI_OP_READ(size,type,len) \
int pci_bus_read_config_##size \
(struct pci_bus *bus, unsigned int devfn, int pos, type *value) \
{ \
int res; \
unsigned long flags; \
u32 data = ; \
if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER; \
spin_lock_irqsave(&pci_lock, flags); \
res = bus->ops->read(bus, devfn, pos, len, &data); \
*value = (type)data; \
spin_unlock_irqrestore(&pci_lock, flags); \
return res; \
}
#define PCI_OP_WRITE(size,type,len) \
int pci_bus_write_config_##size \
(struct pci_bus *bus, unsigned int devfn, int pos, type value) \
{ \
int res; \
unsigned long flags; \
if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER; \
spin_lock_irqsave(&pci_lock, flags); \
res = bus->ops->write(bus, devfn, pos, len, value); \
spin_unlock_irqrestore(&pci_lock, flags); \
return res; \
}
PCI_OP_READ(byte, u8, )
PCI_OP_READ(word, u16, )
PCI_OP_READ(dword, u32, )
PCI_OP_WRITE(byte, u8, )
PCI_OP_WRITE(word, u16, )
PCI_OP_WRITE(dword, u32, )
- 2、bus->ops的實作由arch/i386/pci/common.c中的pci_root_ops結構提供,pci_root_ops結構實際調用的是raw_pci_ops結構:
static int pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *value)
{
return raw_pci_ops->read(, bus->number, devfn, where, size, value);
}
static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 value)
{
return raw_pci_ops->write(, bus->number, devfn, where, size, value);
}
struct pci_ops pci_root_ops = {
.read = pci_read,
.write = pci_write,
};
- 3、raw_pci_ops的mmcfg模式實作在arch/i386/pci/mmconfig.c中定義:
static int pci_mmcfg_read(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 *value)
{
unsigned long flags;
u32 base;
if (!value || (bus > ) || (devfn > ) || (reg > ))
return -EINVAL;
base = get_base_addr(seg, bus, devfn);
if (!base)
return pci_conf1_read(seg,bus,devfn,reg,len,value);
spin_lock_irqsave(&pci_config_lock, flags);
pci_exp_set_dev_base(base, bus, devfn);
switch (len) {
case :
*value = readb(mmcfg_virt_addr + reg);
break;
case :
*value = readw(mmcfg_virt_addr + reg);
break;
case :
*value = readl(mmcfg_virt_addr + reg);
break;
}
spin_unlock_irqrestore(&pci_config_lock, flags);
return ;
}
static int pci_mmcfg_write(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 value)
{
unsigned long flags;
u32 base;
if ((bus > ) || (devfn > ) || (reg > ))
return -EINVAL;
base = get_base_addr(seg, bus, devfn);
if (!base)
return pci_conf1_write(seg,bus,devfn,reg,len,value);
spin_lock_irqsave(&pci_config_lock, flags);
pci_exp_set_dev_base(base, bus, devfn);
switch (len) {
case :
writeb(value, mmcfg_virt_addr + reg);
break;
case :
writew(value, mmcfg_virt_addr + reg);
break;
case :
writel(value, mmcfg_virt_addr + reg);
break;
}
spin_unlock_irqrestore(&pci_config_lock, flags);
return ;
}
- 4、raw_pci_ops的CF8/CFC方式通路模式實作在arch/i386/pci/mmconfig.c中定義:
int pci_conf1_read(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 *value)
{
unsigned long flags;
if (!value || (bus > ) || (devfn > ) || (reg > ))
return -EINVAL;
spin_lock_irqsave(&pci_config_lock, flags);
outl(PCI_CONF1_ADDRESS(bus, devfn, reg), );
switch (len) {
case :
*value = inb( + (reg & ));
break;
case :
*value = inw( + (reg & ));
break;
case :
*value = inl();
break;
}
spin_unlock_irqrestore(&pci_config_lock, flags);
return ;
}
int pci_conf1_write(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 value)
{
unsigned long flags;
if ((bus > ) || (devfn > ) || (reg > ))
return -EINVAL;
spin_lock_irqsave(&pci_config_lock, flags);
outl(PCI_CONF1_ADDRESS(bus, devfn, reg), );
switch (len) {
case :
outb((u8)value, + (reg & ));
break;
case :
outw((u16)value, + (reg & ));
break;
case :
outl((u32)value, );
break;
}
spin_unlock_irqrestore(&pci_config_lock, flags);
return ;
}