BAR寄存器:
- Base Address Register0~5:即BAR寄存器,保存PCI設備使用的地址空間的基地址,保存設備在PCI總線域中的地址,每個設備最多可以有6個基址空間;
- PCI設備復位之后,存放PCI設備需要使用的基地址空間大小,該空間是I /O空間還是存儲器空間等信息;
- 軟件對PCI總線進行配置時,首先獲得BAR寄存器中的初始化信息,之后根據處理器系統的配置,將合理的基地址寫入相應的BAR寄存器中;系統軟件還可以使用該寄存器,獲得PCI設備使用的BAR空間的長度,通過向BAR寄存器寫入0xFFFF-FFFF,之后再讀取該寄存器實現;
- 處理器訪問PCI設備的BAR空間時,需要使用BAR寄存器提供的基地址;但處理器使用存儲器域的地址,而BAR寄存器存放PCI總線域的地址,因此處理器系統並不能直接使用“BAR寄存器+偏移”的方式訪問PCI設備的寄存器空間,而需要將PCI總線域的地址轉換為存儲器域的地址;
- 即使x86處理器系統使能了IOMMU,這兩個地址也並不一定相等,因此處理器系統直接使用這個PCI總線域的物理地址,並不能確保訪問PCI設備BAR空間的正確性;除此之外在Linux系統中,ioremap函數的輸入參數為存儲器域的物理地址,而不能使用PCI總線域的物理地址;
- 而在pci_dev -> resource[bar].start參數中保存的地址已經經過PCI總線域到存儲器域的地址轉換,因此在編寫Linux系統的設備驅動程序時,需要使用pci_dev -> resource[bar].start參數中的物理地址,然后再經過ioremap函數將物理地址轉換為“存儲器域”的虛擬地址,再訪問;
PCI標准定義了三種物理地址空間,分別是IO address, Memory address space以及Configuration address space。其中Configuration address space專門用於配置PCI設備,PCI標准規定了標准的configuration space header,每個PCI設備都必須實現,軟件通過configuration address space,來訪問device的configuration space header,從而可以對device進行配置。
PCI標准要求device將內部寄存器,片上內存等資源map到IO space或者Memory space,從而軟件可以通過發起IO/Memory space的讀寫來訪問device的內部資源。PCI標准在configuration space header里預留了6個32-bit的Base address (BAR),系統軟件可以通過往BAR里設置IO/Memory space的基地址,來實現對device內部資源的影射。在設置好這些BAR后,一旦軟件發起對這些地址的讀寫,都會被device截獲。
PCI規定32位BAR的低4位為read-only,標識了這個device資源在IO space,32-bit的Memory space還是64-bit的Memory space,以及訪問時是否prefetchable。BAR的最后一位標識了是IO space還是Memory space。
BAR[bit:0] = 1 --- IO space
= 0 --- Memory space
在OS啟動前,BIOS會預先program好device的各個BAR的基地址,所以一般OS不需要重新去program BAR,但是當device資源出現沖突時,系統也可以通過寫BAR的值,來重新修改device的資源影射。
PCI標准規定,先往32位BAR里寫0xffffffff,再讀取該BAR,就可以得到該BAR的size。如果讀到的size為0,說明該BAR沒有被device使用。
Power-up software can determine how much address space the device requires by writing a
value of all 1's to the register and then reading the value back. The device will return 0's in
all don't-care address bits, effectively specifying the address space required. Unimplemented
Base Address registers are hardwired to zero.
如果該BAR是64-bit Memory space,則下一個BAR被認為是64位的高32位,64位size的高32位,也可以通過同樣的方法得到。
64-bit (memory) Base Address registers can be handled the same, except that the second
32-bit register is considered an extension of the first; i.e., bits 32-63. Software writes
0FFFFFFFFh to both registers, reads them back, and combines the result into a 64-bit value.
Size calculation is done on the 64-bit value.
讀取base,size以及type的偽代碼: u32 base, size; pci_resource_type type; (io_space, mem32_space, mem64_space) boolean prefetchable; //得到base address pci_config_read(device, BARi, &base); //得到size pci_config_write(device, BAR, 0xffffffff); pci_config_read(device, BAR, &size); //重新寫回base address,有需要也可以是其他的地址 pci_config_write(device, BAR, base); //確定type if (base & 0x1) type = io_space; else if((base & 0x6) == 0x4) type = mem64_space; else type = mem32_space; prefetchable = (base & 0x8) ? true : false; //如果是64-bit Memory space,得到base address和size的高32位,方法相同 if (type == mem64_space) { u64 base64 = base; u64 size64 = size; //BAR+4指向下一個BAR的位置,此時被認為是64位的高32位 pci_config_read(device, BAR+4, &base); base64 |= ((u64)base << 32); pci_config_write(device, BAR+4, 0xffffffff); pci_config_read(device, BAR+4, &size); size64 |= ((u64)size << 32); }