1. 程式人生 > >1.4 x86 CPU地址空間分配和暫存器訪問

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;
}