https://shaocheng.li/posts/2017/11/27/
x86 計算機的 PCI 總線結
Linux PCI Express 配置空間讀寫內核實現
http://www.ilinuxkernel.com/files/5/Linux_PCI_Express_Kernel_RW.htm
[root@localhost ~]# grep 05:00.0 /proc/iomem
e9200000-e92fffff : 0000:05:00.0
80000200000-800002fffff : 0000:05:00.0
80000300000-80007afffff : 0000:05:00.0
80007b00000-80007b1ffff : 0000:05:00.0
80007b20000-8000829ffff : 0000:05:00.0
800082a0000-80008a1ffff : 0000:05:00.0
80008a20000-80008a27fff : 0000:05:00.0
[root@localhost ~]# ls /sys/bus/pci/devices/0000\:05\:00.0/c
class consistent_dma_mask_bits current_link_width
config current_link_speed
[root@localhost ~]# ls /sys/bus/pci/devices/0000\:05\:00.0/con
config consistent_dma_mask_bits
[root@localhost ~]# hexdump /sys/bus/pci/devices/0000\:05\:00.0/config
0000000 19e5 0200 0406 0010 0045 0200 0008 0000
0000010 000c 07b0 0800 0000 000c 08a2 0800 0000
0000020 000c 0020 0800 0000 0000 0000 19e5 d139
0000030 0000 e640 0040 0000 0000 0000 00ff 0000
0000040 8010 0002 8fe2 1000 2937 0010 f103 0043
0000050 0008 0103 0000 0000 0000 0000 0000 0000
0000060 0000 0000 0392 0000 0000 0000 000e 0000
0000070 0003 001f 0000 0000 0000 0000 0000 0000
0000080 a005 018a 0000 0000 0000 0000 0000 0000
0000090 0000 0000 0000 0000 0000 0000 0000 0000
00000a0 b011 801f 0002 0000 4002 0000 0000 0000
00000b0 c001 f803 0000 0000 0000 0000 0000 0000
00000c0 0003 8028 3237 ff78 0000 0000 0000 0000
00000d0 0000 0000 0000 0000 0000 0000 0000 0000
*
0000100 0001 1501 0000 0000 0000 0040 2030 0046
0000110 0000 0000 c000 0000 00a0 0000 0001 0400
0000120 0003 0060 0000 0501 0b82 1240 0000 0000
0000130 0000 0000 0000 0000 0000 0000 0000 0000
*
0000150 000e 2001 0000 0000 0000 0000 0000 0000
0000160 0000 0000 0000 0000 0000 0000 0000 0000
*
0000200 0010 3101 0002 0000 0010 0000 0078 0078
0000210 0000 0000 0001 0001 0000 375e 0553 0000
0000220 0010 0000 000c 07b2 0800 0000 000c 082a
0000230 0800 0000 000c 0030 0800 0000 0000 0000
0000240 0000 0000 0000 0000 0000 0000 0000 0000
*
0000310 0019 4e01 0000 0000 0000 0000 0000 0000
0000320 0000 0000 0000 0000 0000 0000 0000 0000
*
00004e0 0003 4f01 9beb ffa4 91ff 44a1 0000 0000
00004f0 0017 6001 0005 0000 0000 0000 0000 0000
0000500 0000 0000 0000 0000 0000 0000 0000 0000
*
0000600 000b 6301 0000 0280 0000 0000 0000 0000
0000610 0000 0000 0000 0000 0000 0000 0000 0000
*
0000630 000d 0001 0000 0000 0000 0000 0000 0000
0000640 0000 0000 0000 0000 0000 0000 0000 0000
*
0001000
[root@localhost ~]#
1 PCI及PCI-E配置空間介紹
PCI-E是用來互聯如計算和通信平台應用中外圍設備的第三代高性能I/O總線。PCI-E采用了與PCI相同的使用模型和讀寫(load-store)通信模型,支持各種常見的事務,如存儲器讀/寫、IO讀/寫和配置讀/寫事務。其存儲器、IO和配置地址空間與PCI的地址空間相同。PCI Express與PCI系統是軟件向后兼容的。
PCI-E的配置空間大小為4096字節,如圖1所示。其中前256字節是與PCI兼容的配置寄存器,該區域可以用以下兩種機制訪問:
- PCI配置訪問機制。
- PCI Express增強型配置機制。
圖1 PCI-E配置空間
Memory-mapped I/O (MMIO)與port I/O
MMIO和port I/O(也稱為port-mapped I/O或PMIO)是兩種CPU與外設之間進行I/O操作的方式。
Port I/O是通過特殊的CPU指令來進行I/O操作,在x86架構上,可以通過指令in和out在特定的端口上進行I/O讀寫。I/O設備擁有與內存不同的地址空間,實現的方式是通過在CPU上額外的I/O pin或者將整個總線賦予端口。
MMIO即內存映射I/O,它是PCI規范一部分,I/O設備被放置在內存空間而不是I/O空。從處理器角度看,內存映射I/O后系統設備訪問起來和內存一樣。這樣訪問AGP/PCI-E顯卡上的幀緩存,BIOS,PCI設備就可以使用讀寫內存一樣的匯編指令完成,簡化了程序設計的難度和接口的復雜性。
對軟件人員來說,MMIO比Port I/O更方便使用。
2 PCI-E配置空間讀寫在內核的實現
用戶空間的兩個命令lspci和setpci來查看/修改PCI及PCI-E配置空間。用戶命令執行的結果,是由內核來確定。那么我們關心一個問題:內核是如何真正去讀取和修改配置空間的?
2.1 內核API接口
Linux內核提供了以下PCI/PCI-E配置空間訪問接口,在驅動編寫過程中,我們可以直接使用下面函數。這些
- pci_{read,write}_config_byte()
- pci_{read,write}_config_word()
- pci_{read,write}_config_dword()
函數的定義在文件include/linux/pci.h中。
00513: static inline int pci_read_config_byte(struct pci_dev *dev, int where,
00513: u8 *val)
00514: {
00515: return pci_bus_read_config_byte (dev- >bus, dev- >devfn, where, val);
00516: }
00517: static inline int pci_read_config_word(struct pci_dev *dev, int where,
00517: u16 * val)
00518: {
00519: return pci_bus_read_config_word (dev- >bus, dev- >devfn, where, val);
00520: }
00521: static inline int pci_read_config_dword(struct pci_dev *dev,
00521: int where, u32*val)
00522: {
00523: return pci_bus_read_config_dword (dev- >bus, dev- >devfn, where, val);
00524: }
00525: static inline int pci_write_config_byte(struct pci_dev *dev, int where,
00525: u8 val)
00526: {
00527: return pci_bus_write_config_byte (dev- >bus, dev- >devfn, where, val);
00528: }
00529: static inline int pci_write_config_word(struct pci_dev *dev,
00529: int where, u16 val)
00530: {
00531: return pci_bus_write_config_word (dev- >bus, dev- >devfn, where, val);
00532: }
00533: static inline int pci_write_config_dword(struct pci_dev *dev,
00533: int where, u32 val)
00534: {
00535: return pci_bus_write_config_dword (dev- >bus, dev- >devfn, where, val);
00536: }
2.2 內核API實現
在PCI/PCI-E 配置空間讀寫API接口中,我們看到是對pci_bus_{read, write}_config_{byte, word, dword}的的封裝。這些函數由drivers/pci/access.c中以宏的方式定義。
00024: #define PCI_OP_READ(size,type,len) \
00025: int pci_bus_read_config_##size \
00026: (struct pci_bus *bus , unsigned int devfn, int pos , type *value) \
00027: { \
00028: int res ; \
00029: unsigned long flags ; \
00030: u32 data = 0; \
00031: if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER; \
00032: spin_lock_irqsave(&pci_lock, flags ); \
00033: res = bus - >ops- >read(bus , devfn, pos , len, &data ); \
00034: *value = (type)data ; \
00035: spin_unlock_irqrestore(&pci_lock, flags ); \
00036: return res ; \
00037: }
00038:
00039: #define PCI_OP_WRITE(size,type,len) \
00040: int pci_bus_write_config_##size \
00041: (struct pci_bus *bus , unsigned int devfn, int pos , type value) \
00042: { \
00043: int res ; \
00044: unsigned long flags ; \
00045: if (PCI_##size##_BAD) return PCIBIOS_BAD_REGISTER_NUMBER; \
00046: spin_lock_irqsave(&pci_lock, flags ); \
00047: res = bus - >ops- >write(bus , devfn, pos , len, value); \
00048: spin_unlock_irqrestore(&pci_lock, flags ); \
00049: return res ; \
00050: }
00059: EXPORT_SYMBOL(pci_bus_read_config_byte);
00060: EXPORT_SYMBOL(pci_bus_read_config_word);
00061: EXPORT_SYMBOL(pci_bus_read_config_dword);
00062: EXPORT_SYMBOL(pci_bus_write_config_byte);
00063: EXPORT_SYMBOL(pci_bus_write_config_word);
00064: EXPORT_SYMBOL(pci_bus_write_config_dword);
pci_bus_{read, write}_config_{byte, word, dword}()等函數,調用的是bus->ops->write、bus->ops->read方法。顯然,現在的bus總線是PCI/PCI-E,我們就關注內核定義PCI/PCI-E總線的讀寫操作方法。
注:Linux內核沒有專門將PCI-E列為一種總線,而是將PCI-E合並到PCI總線中。
2.3 PCI總線讀寫方法
PCI總線讀寫方法為pci_root_ops,對應的讀寫函數分別為pci_read()、pci_write()。實現在文件arch/i386/pci/common.c中。
00036: static int pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32
00036: *value)
00037: {
00038: return raw_pci_ops - >read(pci_domain_nr(bus), bus- >number,
00039: devfn, where, size, value);
00040: }
00041:
00042: static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size,
00042: u32 value)
00043: {
00044: return raw_pci_ops - >write(pci_domain_nr(bus), bus- >number,
00045: devfn, where, size, value);
00046: }
00047:
00048: struct pci_ops pci_root_ops = {
00049: .read = pci_read,
00050: .write = pci_write,
00051: };
pci_read()、pci_write()依賴於raw_pci_ops全局變量。
2.3.1 raw_pci_ops全局變量的設置
內核在啟動時,會執行pci_access_init()函數,在文件arch/i386/pci/init.c中。該函數中,確定了raw_pci_ops值。
00005: / * arch_initcall has too random ordering, so call the initializers
00006: in the right sequence from here. */
00007: static __init int pci_access_init(void)
00008: {
00009: #ifdef CONFIG_PCI_MMCONFIG
00010: pci_mmcfg_init();
00011: #endif
00012: dmi_check_pciprobe();
00013:
00014: if (raw_pci_ops )
00015: return 0;
00016:
00017: #ifdef CONFIG_PCI_BIOS
00018: pci_pcbios_init();
00019: #endif
00020: / *
00021: * don't check for raw_pci_ops here because we want pcbios as last
00022: * fallback, yet it's needed to run first to set pcibios_last_bus
00023: * in case legacy PCI probing is used. otherwise detecting peer busses
00024: * fails.
00025: */
00026: #ifdef CONFIG_PCI_DIRECT
00027: pci_direct_init();
00028: #endif
00029: return 0;
00030: } ? end pci_access_init ?
00031: arch_initcall(pci_access_init);
對於訪問PCI空間,通過Port I/O方式則可以實現完全訪問。但要訪問全部的PCI-E配置空間,則需要MMIO方式。MMIO方式訪問,則需要Linux內核支持。在編譯內核時,選中以下選項即可。
Bus options (PCI etc.) --->
--- PCI support
[*] Support mmconfig PCI config space access
即需要選中“Support mmconfig PCI config space access”。 若沒有選中該項,則用戶通過lspci或setpci命令,訪問不到PCI-E的擴展配置空間(256~4096字節)。
為了訪問PCI-E擴展配置空間,pci_access_init()函數會調用pci_mmcfg_init()。於是將raw_pci_ops的值設為pci_mmcfg,代碼都在文件arch/i386/pci/mmconfig.c中。
00152: void __init pci_mmcfg_init(void)
00153: {
... ...
00173: raw_pci_ops = &pci_mmcfg ;
... ...
00176: } ? end pci_mmcfg_init ?
00147: static struct pci_raw_ops pci_mmcfg = {
00148: .read = pci_mmcfg_read,
00149: .write = pci_mmcfg_write,
00150: };
00151:
若內核中沒有選中“Support mmconfig PCI config space access”,則raw_pci_ops方法為:pci_direct_conf1或pci_direct_conf2。通常情況下,使用pci_direct_conf1。代碼在文件/arc/i386/pci/direct.c中。
00257: void __init pci_direct_init(void)
00258: {
00259: struct resource *region, *region2;
... ...
00267: if (pci_check_type1()) {
00268: printk(KERN_INFO "PCI: Using configuration type 1\n");
00269: raw_pci_ops = &pci_direct_conf1;
00270: return;
00271: }
... ...
00284: if (pci_check_type2()) {
00285: printk(KERN_INFO "PCI: Using configuration type 2\n");
00286: raw_pci_ops = &pci_direct_conf2 ;
00287: return;
00288: }
00293: } ? end pci_direct_init ?
00079: struct pci_raw_ops pci_direct_conf1 = {
00080: .read = pci_conf1_read,
00081: .write = pci_conf1_write,
00082: };
00171: #undef PCI_CONF2_ADDRESS
00172:
00173: static struct pci_raw_ops pci_direct_conf2 = {
00174: .read = pci_conf2_read,
00175: .write = pci_conf2_write,
00176: };
2.3.2 Port I/O方式訪問配置空間
Port I/O方式也稱為直接方式訪問。
PCI規范規定,直接操作port讀取PCI配置信息時,通過CONFIG_ADDRESS和CONFIG_DATA;兩個寄存器進行。CONFIG_ADDRESS的值為0xCF8,CONFIG_DATA的值為0xCFC,兩個寄存器都為32bit。兩個寄存器就是對應x86架構中的端口號。圖2為CONFIG_ADDRESS寄存器格式。
圖2 CONFIG_ADDRESS寄存器格式
bit31是使能對PCI Bus CONFIG_DATA的訪問;
bit 30~24為保留,為只讀,訪問時返回值為0;
bit 23~16是Bus號;
bit 15~10是設備號;
bit 10~8是功能號;
bit 7~2是配置空間中的寄存器,單位為DWORD。
bit 1~0為只讀,讀取時放回為0。
這樣直接訪問PCI配置空間時,分為兩步:
第一步是向CONFIG_ADDRESS寄存器(端口0xCF8)寫入要讀/寫的位置;
第二步是從CONFIG_DATA寄存器(端口0xCFC)讀/寫所需要數據。
Linux內核對PCI配置空間直接訪問的實現函數分別為pci_conf1_read()/pci_conf1_write()和pci_conf2_read()/pci_conf2_write(),分別對應讀寫Type 0和Type 1的配置空間。對於我們的PCI-E外設來說,是Type 0型配置空間。這里我們只關注Type 0。
函數pci_conf1_read()和pci_conf1_write()函數在文件arch/i386/pci/direct.c中。
00017: int pci_conf1_read(unsigned int seg, unsigned int bus,
00018: unsigned int devfn, int reg, int len, u32 *value)
00019: {
00020: unsigned long flags;
00021:
00022: if ((bus > 255) || (devfn > 255) || (reg > 255)) {
00023: *value = - 1;
00024: return - EINVAL;
00025: }
00026:
00027: spin_lock_irqsave(&pci_config_lock , flags);
00028:
00029: outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8);
00030:
00031: switch (len) {
00032: case 1:
00033: *value = inb(0xCFC + (reg & 3));
00034: break;
00035: case 2:
00036: *value = inw(0xCFC + (reg & 2));
00037: break;
00038: case 4:
00039: *value = inl(0xCFC);
00040: break;
00041: }
00042:
00043: spin_unlock_irqrestore(&pci_config_lock , flags);
00044:
00045: return 0;
00046: } ? end pci_conf1_read ?
00047:
00048: int pci_conf1_write(unsigned int seg, unsigned int bus,
00049: unsigned int devfn, int reg, int len, u32 value)
00050: {
00051: unsigned long flags;
00052:
00053: if ((bus > 255) || (devfn > 255) || (reg > 255))
00054: return - EINVAL;
00056: spin_lock_irqsave(&pci_config_lock , flags);
00057:
00058: outl(PCI_CONF1_ADDRESS(bus, devfn, reg), 0xCF8);
00059:
00060: switch (len) {
00061: case 1:
00062: outb((u8)value, 0xCFC + (reg & 3));
00063: break;
00064: case 2:
00065: outw((u16)value, 0xCFC + (reg & 2));
00066: break;
00067: case 4:
00068: outl((u32)value, 0xCFC);
00069: break;
00070: }
00071:
00072: spin_unlock_irqrestore(&pci_config_lock , flags);
00073:
00074: return 0;
00075: } ? end pci_conf1_write ?
00076:
2.3.3 MMIO方式訪問配置空間
Port I/O方式只能訪問PCI配置空間,而不能訪問PCI-E擴展配置空間(257~4096字節),此時只能通過MMIO方式。Linux內核中的MMIO實現讀/寫分別對應函數pci_mmcfg_write()和pci_mmcfg_read()。函數在文件arch/i386/pci/mmconfig.c中。
00071: static int pci_mmcfg_read(unsigned int seg, unsigned int bus,
00072: unsigned int devfn, int reg, int len, u32 *value)
00073: {
00074: unsigned long flags;
00075: u32 base;
00076:
00077: if ((bus > 255) || (devfn > 255) || (reg > 4095)) {
00078: err: *value = - 1;
00079: return - EINVAL;
00080: }
00081:
00082: if (reg < 256)
00083: return pci_conf1_read(seg,bus,devfn,reg,len,value);
00084:
00085: base = get_base_addr(seg, bus, devfn);
00086: if (! base)
00087: goto ↑err;
00088:
00089: spin_lock_irqsave(&pci_config_lock , flags);
00090:
00091: pci_exp_set_dev_base(base, bus, devfn);
00092:
00093: switch (len) {
00094: case 1:
00095: *value = mmio_config_readb(mmcfg_virt_addr + reg);
00096: break;
00097: case 2:
00098: *value = mmio_config_readw(mmcfg_virt_addr + reg);
00099: break;
00100: case 4:
00101: *value = mmio_config_readl(mmcfg_virt_addr + reg);
00102: break;
00103: }
00104:
00105: spin_unlock_irqrestore(&pci_config_lock , flags);
00106:
00107: return 0;
00108: } ? end pci_mmcfg_read ?
00110: static int pci_mmcfg_write(unsigned int seg, unsigned int bus,
00111: unsigned int devfn, int reg, int len, u32 value)
00112: {
00113: unsigned long flags;
00114: u32 base;
00115:
00116: if ((bus > 255) || (devfn > 255) || (reg > 4095))
00117: return - EINVAL;
00118:
00119: if (reg < 256)
00120: return pci_conf1_write(seg,bus,devfn,reg,len,value);
00121:
00122: base = get_base_addr(seg, bus, devfn);
00123: if (! base)
00124: return - EINVAL;
00125:
00126: spin_lock_irqsave(&pci_config_lock , flags);
00127:
00128: pci_exp_set_dev_base(base, bus, devfn);
00129:
00130: switch (len) {
00131: case 1:
00132: mmio_config_writeb(mmcfg_virt_addr + reg, value);
00133: break;
00134: case 2:
00135: mmio_config_writew(mmcfg_virt_addr + reg, value);
00136: break;
00137: case 4:
00138: mmio_config_writel(mmcfg_virt_addr + reg, value);
00139: break;
00140: }
00141:
00142: spin_unlock_irqrestore(&pci_config_lock , flags);
00143:
00144: return 0;
00145: } ? end pci_mmcfg_write ?
若訪問的配置空間在前面256字節范圍內,則直接調用直接訪問方式(Port I/O)。若訪問PCI-E擴展配置空間,則首先通過get_base_addr()函數獲取設備對應的內存空間物理地址,然后通過pci_exp_set_dev_base()函數將物理地址映射到一個線性地址,最后通過mmio_config_{read, write}{b, w, l}執行真正的讀寫。
1. get_base_addr()
00028 / *
00029: * Functions for accessing PCI configuration space with MMCONFIG accesses
00030: */
00031: static u32 get_base_addr(unsigned int seg, int bus, unsigned devfn)
00032: {
00033: int cfg_num = - 1;
00034: struct acpi_table_mcfg_config *cfg;
00035:
00036: while (1) {
00037: ++cfg_num;
00038: if (cfg_num >= pci_mmcfg_config_num ) {
00039: break;
00040: }
00041: cfg = &pci_mmcfg_config [cfg_num];
00042: if (cfg- >pci_segment_group_number ! = seg)
00043: continue;
00044: if ((cfg- >start_bus_number <= bus) &&
00045: (cfg- >end_bus_number >= bus))
00046: return cfg- >base_address;
00047: }
00048:
00049: / * Handle more broken MCFG tables on Asus etc.
00050: They only contain a single entry for bus 0- 0. Assume
00051: this applies to all busses. */
00052: cfg = &pci_mmcfg_config [0];
00053: if (pci_mmcfg_config_num == 1 &&
00054: cfg- >pci_segment_group_number == 0 &&
00055: (cfg- >start_bus_number | cfg- >end_bus_number) == 0)
00056: return cfg- >base_address;
00057:
全局變量pci_mmcfg_config是所有PCI/PCI-E設備的MMIO映射表,MMIO映射表是內核根據BIOS POST結構初始化PCI總線時設置好,內核讀取分配的值即可。
2. pci_exp_set_dev_base()
通過get_base_addr()獲取到的地址是物理地址,為了能讀取,還需通過桉樹pci_exp_set_dev_base(base, bus, devfn);理地址轉換為邏輯地址。
00062: static inline void pci_exp_set_dev_base(unsigned int base, int bus, int
00062: devfn)
00063: {
00064: u32 dev_base = base | (bus << 20) | (devfn << 12);
00065: if (dev_base != mmcfg_last_accessed_device ) {
00066: mmcfg_last_accessed_device = dev_base;
00067: set_fixmap_nocache(FIX_PCIE_MCFG, dev_base);
00068: }
00069: }
文件include/asm-i386/fixmap.h。
00100: / *
00101: * Some hardware wants to get fixmapped without caching.
00102: */
00103: #define set_fixmap_nocache(idx, phys) \
00104: __set_fixmap(idx, phys, PAGE_KERNEL_NOCACHE)
00105:
文件arch/i386/mm/pgtable.c。
00140: void __set_fixmap (enum fixed_addresses idx, unsigned long phys, pgprot_t
00140: flags)
00141: {
00142: unsigned long address = __fix_to_virt(idx);
00143:
00144: if (idx >= __end_of_fixed_addresses) {
00145: BUG();
00146: return;
00147: }
00148: set_pte_pfn(address, phys >> PAGE_SHIFT, flags);
00149: }
00150:
00023: #define mmcfg_virt_addr ((void __iomem *) fix_to_virt(FIX_PCIE_MCFG))
2.4 用戶接口在內核的實現
前面我們已經提到可以通過lspci和setpci命令來讀寫PCI/PCI-E配置。而這些命令的實現是基於內核提供的/sysfs接口或/proc接口。
內核為PCI/PCI-E總線提供的/sysfs讀寫方法如下,文件drivers/pci/pci-sysfs.c。
00510: static struct bin_attribute pci_config_attr = {
00511: .attr = {
00512: .name = "config",
00513: .mode = S_IRUGO | S_IWUSR,
00514: .owner = THIS_MODULE,
00515: },
00516: .size = 256,
00517: .read = pci_read_config,
00518: .write = pci_write_config,
00519: };
00520:
00521: static struct bin_attribute pcie_config_attr = {
00522: .attr = {
00523: .name = "config",
00524: .mode = S_IRUGO | S_IWUSR,
00525: .owner = THIS_MODULE,
00526: },
00527: .size = 4096,
00528: .read = pci_read_config,
00529: .write = pci_write_config,
00530: };
00531:
pci_read_config()和pci_write_config()函數進而調用pci_user_write_config_{dword, word, byte}。我們來看一下setpci命令執行時(圖3),內核棧信息和lspci棧信息(圖4)。
由棧信息我們可以看出,函數最終調用pci_conf1_write()函數。也就是/sysfs提供的讀寫接口,也最終是使用Port I/O和MMIO方式。
圖3 pci_conf1_write()函數調用棧
圖4 pci_mmcfg_read()函數調用棧
下面是Intel ICH9系列南橋和Intel Cantiga北橋系列的獲得Vonder ID和Device ID的PCI訪問方法。
HDD:
mov dx,cf8h ;Bus:0,device:31,Function:2
out dx,8000FA00h ;
add dx,04h ;
in eax,dx ;
那么在AH中就是Device ID,在AL 中就是Vendor ID.
獨立顯卡:
mov dx,cf8h ;Bus:0,device:1,Function:0
out dx,8000800h ;
mov dx,cfch ;
in eax,dx ;
那么在AH中就是Device ID,在AL 中就是Vendor ID.
集成顯卡:
mov dx,cf8h ;Bus:0,device:1,Function:0
out dx,80001000h ;
mov dx,cfch ;
in eax,dx ;
那么在AH中就是Device ID,在AL 中就是Vendor ID.
網卡:
mov dx,cf8h ;Bus:0,device:1,Function:0
out dx,8000C800h ;
mov dx,cfch ;
in eax,dx ;
那么在AH中就是Device ID,在AL 中就是Vendor ID.
BIOS入門的第一課大部分都會遇到PCI的作業,前輩們往往都會出個簡單的作業,要求我們去讀寫PCI configuaration space,到底我們該如何去做呢?
根據PCI Local Bus Specification 3.0有提到,"Every device, other than host bus bridges, must implement Configuration Address Space. Host bus bridges may optionally implement Configuration Address Space. In the Configuration Address Space, each function is assigned a unique 256-byte space that is accessed differently than I/O or Memory Address Spaces"
也就是說讀寫PCI的方式有兩種,一種是透過I/O(CF8/CFC)而另一種則是利用MMIO的方式來去讀寫PCI configuration space.
在介紹這兩種讀寫方法以前,我們先來看PCI spec.如何定義Configuration commands。為了支援階層式的PCI buses,在此使用兩種類型的configuration transactions,如Figure 1所示。
Figure 1
在Figure 1中我們可以看到兩種不同的configuration commands,分別為Type0和Type1,其中
Type0: A Type 0 configuration transaction (when AD[1::0] = “00”) is used to select a device on the bus where the transaction is being run.
Type1: A Type 1 configuration transaction (when AD[1::0] = “01”) is used to pass a configuration request to another bus segment.
而IDSEL(Initialization Device Select)是指"it used as a chip select during configuration
read and write transactions." 這部分會連接到PCI AD[31:11],是由硬體拉線所決定的。
其中IDSEL可以辨別PCI的身分,當硬體解出來的configuration cycle為Type0時,會做以下兩件事:1.遮罩[31:11],2.解碼Device number。相反的,當硬體解出的configuration cycle為Type1時,他會做以下的事:1.當bus number和secondary bus number一致時,會將Type1轉成Type0,也就是Bit0設為1,並做Type0該做的事;當bus number和secondary bus number不一致時,則會往下一層送。
透過IDSEL辨別Type0和Type1的方式,我們可以完成PCI的Scan,但在Type0的configuration transactions中好像沒有定義Bus number與Devce number,那麼如何去做transactions呢?還記得Type0的定義,他是在相同的bus下去選擇device,而device number我們又可以透過解碼的方式去獲得,因此就不會有找不到的問題。
Software Generation of Configuration Transactions
系統必須提供一種機制來讓軟體有辦法去Access PCI configuration space,前面有提到可以透過I/O space及MMIO space的方式去存取,但PCI device還沒分配resource之前,還是只能透過I/O space的方式去access。
"Two DWORD I/O locations are used to generate configuration transactions for PC-AT compatible systems. The first DWORD location (CF8h) references a read/write register that is named CONFIG_ADDRESS. The second DWORD address (CFCh) references a read/write register named CONFIG_DATA."
Figure 2為針對CONFIG_ADDRESS所定義的Layout,其中:
Figure 2
where
Bit[31]: It is an enable flag for determining when accesses to CONFIG_DATA are to be translated to configuration transactions on the PCI bus.
Bit[30:24]: Reserved
Bit[23:16]: Bits 23 through 16 choose a specific PCI bus in the system.
Bit[15:11]: Bits 15 through 11 choose a specific device on the bus.
Bit[10:8]: Bits 10 through 8 choose a specific function in a device.
Bit[7:2]: Bits 7 through 2 choose a DWORD in the device's Configuration Space.
Bit[1:0]: Bits 1 and 0 are read-only and must return 0's when read.
由上面定義可以得知最多支援的Bus number為256(2的8次方),最多支援的device number為32(2的5次方),最多支援的function number為8(2的3次方),可以讀取的configuration space為256(2的8次方),速記為8538。
看到這邊,或許會對configuration space有疑問,在Figure 2不是只有用Bit[7:2]去定義嗎,為什麼可以讀到256個呢?這是因為上面定義Bit[1:0]在Read的時候必須是0,所以如果我們要去Read/Write的space剛好在Bit[1:0]不為0的時候只能利用偏移的方式去做讀取,我們之後會有一個小小的範例做說明。
接著我們看這兩種Type的configuration commands如何與SW做結合。
Figure 3
自Figure 3我們可以看到,假設IDSEL辨別為Type0,自然就知道Bus number,而device number可以透過解碼方式獲得,並填到Bit[31:11]中的其中一個,剩下的Function number和Register number則複製到PCI AD BUS當中。
如果IDSEL辨別為Type1,則是將CONFIG_ADDRESS全部複製到PCI AD line當中,當然Bit0也必須是1。
PCI R/W sample:
參考Figure 4,如下:
Figure 4
如圖Figure 所示,假設我們要讀取Bus00, Dev1B,Fun00的Offset3C(Interrupt Line),那麼我們應該如何做呢?步驟描述如下:
Step1:
我們知道透過I/O space(CF8/CFC)可以Read/Write Configuration space,其中把要讀取該Space的PFA填入Config_Address(CF8)後,在Config_Data(CFC)就可以得到該資料。
Step2:
有了步驟1的知識後,我們要先計算出PFA,回顧Figure 2所示,我們必須將要讀取的BUS,Dev,Fun及Offset填入Config_Address所對應的Layout,其中Bit31必須為1,Bit[1:0]必須為0(如果剛好不為0可以用偏移方式讀取),其分析如下:
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 |
PFA = 8000D83C h
以Tool RW來示範的話就是利用I/O CF8填入PFA,然後在CFC的Offset 00h可以得到Interrupt Line的值16h,如圖Figure 5所示。
Figure 5
如果用組合語言來表示我們可以描述如下:
mov dx, cf8h
mov eax, 8000d83ch (PFA)
out dx, eax
mov dx, cfch
in eax, dx
如此,我們可以在EAX的Offset 00h得到我們要讀取的值。