1.4 x86 CPU地址空間分配和暫存器訪問
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 = 0; \
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, 1)
PCI_OP_READ(word, u16, 2)
PCI_OP_READ(dword, u32, 4)
PCI_OP_WRITE(byte, u8, 1)
PCI_OP_WRITE(word, u16, 2)
PCI_OP_WRITE(dword, u32, 4)
- 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(0, 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(0, 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 > 255) || (devfn > 255) || (reg > 4095))
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 1:
*value = readb(mmcfg_virt_addr + reg);
break;
case 2:
*value = readw(mmcfg_virt_addr + reg);
break;
case 4:
*value = readl(mmcfg_virt_addr + reg);
break;
}
spin_unlock_irqrestore(&pci_config_lock, flags);
return 0;
}
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 > 255) || (devfn > 255) || (reg > 4095))
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 1:
writeb(value, mmcfg_virt_addr + reg);
break;
case 2:
writew(value, mmcfg_virt_addr + reg);
break;
case 4:
writel(value, mmcfg_virt_addr + reg);
break;
}
spin_unlock_irqrestore(&pci_config_lock, flags);
return 0;
}
- 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 > 255) || (devfn > 255) || (reg > 255))
return -EINVAL;
spin_lock_irqsave(&pci_config_lock, flags);
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;
}
spin_unlock_irqrestore(&pci_config_lock, flags);
return 0;
}
int pci_conf1_write(unsigned int seg, unsigned int bus,
unsigned int devfn, int reg, int len, u32 value)
{
unsigned long flags;
if ((bus > 255) || (devfn > 255) || (reg > 255))
return -EINVAL;
spin_lock_irqsave(&pci_config_lock, flags);
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;
}
spin_unlock_irqrestore(&pci_config_lock, flags);
return 0;
}