目錄
7.1訪問PCI/PCIE 設(shè)備
7.1.1與PCI/PCIE設(shè)備通信的機(jī)制
7.1.2支持訪問PCI/PCIE設(shè)備的Protocol
7.1.3訪問PCI/PCIE設(shè)備示例
進(jìn)行項目開發(fā)、構(gòu)建產(chǎn)品框架的時候,最開始需要考慮的就是采用哪種通信方式讓軟件可以訪問外部設(shè)備(簡稱外設(shè))。計算機(jī)經(jīng)過多年的發(fā)展,提供了非常豐富的通信協(xié)議供程序員選擇。從古老的串口協(xié)議,到使用廣泛的 PCI/PCIE 協(xié)議,再到現(xiàn)在無處不在的 USB 協(xié)議等,可供選擇的方式實在太多。
在 Legacy BIOS 下,第三方開發(fā)者編寫訪問外設(shè)的代碼是件非常辛苦的事情。主要原因在于,Legacy BIOS 對很多協(xié)議支持得并不好。以筆者常用的 SMBus 協(xié)議為例,直到現(xiàn)在,也沒有 BIOS 廠家提供標(biāo)準(zhǔn)的中斷接口。大多數(shù)時候,只能通過閱讀主板芯片組規(guī)格書,了解與 SMBus 協(xié)議相關(guān)的寄存器信息,再根據(jù)標(biāo)準(zhǔn)的 SMBus 總線讀寫協(xié)議編寫代碼。市場上主板芯片組太多,這種方法寫出來的代碼,兼容性、穩(wěn)定性都不理想,并且工作量非常大。
UEFI BIOS 的出現(xiàn),解決了上述這些問題。從 UEFI 標(biāo)準(zhǔn)和 EDK2 的源碼也可以看出,常用的總線協(xié)議如 PCI/PCIE、SMBus、串口等,UEFI 都已經(jīng)提供了支持。對于依賴于 BIOS 接口進(jìn)行產(chǎn)品開發(fā)的廠商來說,這是一個非常好的消息,產(chǎn)品的開發(fā)速度將大幅提高,穩(wěn)定性和兼容性也能得到保障。
本章將介紹如何在 UEFI 環(huán)境下使用 UEFI 規(guī)范提供的接口(即各類 Protocol),通過 PCI/PCIE、SMBus 和串口訪問外設(shè)。
7.1 訪問 PCI/PCIE 設(shè)備
PCI(Peripheral Component Interconnect)是一種高速的局部總線。其主要目的是連接周邊設(shè)備,將低速的設(shè)備與高速的處理器結(jié)合起來,以解決用戶對數(shù)據(jù)傳輸速率越來越高的要求。PCIE 總線是在 PCI 總線上繼承發(fā)展來的,其將信號傳輸方式從并行改為了串行, 傳輸速率也突飛猛進(jìn),PCI 的理論帶寬為 133MB/s,而 PCIE4.0 x16 的帶寬達(dá)到了 64GB/s。
從硬件結(jié)構(gòu)角度看,PCI 和 PCIE 有很大的不同。PCI 總線采用并行總線結(jié)構(gòu),而 PCIE 總線使用了高速差分總線結(jié)構(gòu),使用端到端的連接方式,這使得兩者采用的拓?fù)浣Y(jié)構(gòu)差異較大。隨著技術(shù)的發(fā)展,目前市場上 PCI 設(shè)備越來越少,很多主板現(xiàn)在只提供 PCIE 的接口了。
這些差異對在 UEFI 下進(jìn)行編程影響不大。UEFI 系統(tǒng)已經(jīng)屏蔽了這些差異,提供了一致的訪問接口,下面詳細(xì)介紹如何訪問 PCI/PCIE 設(shè)備。
7.1.1與 PCI/PCIE設(shè)備通信的機(jī)制
PCI 協(xié)議和 PCIE 協(xié)議經(jīng)過多年的發(fā)展,其內(nèi)容已經(jīng)非常龐大。本書主要論述的是如何在 UEFI 下進(jìn)行編程。站在軟件工程師的角度,在訪問 PCI/PCIE 設(shè)備時,實際上只要回答以下兩個問題就可以了。
?如何在系統(tǒng)中找到需要訪問的設(shè)備?
?找到設(shè)備后,如何訪問設(shè)備內(nèi)的寄存器或其他資源?
UEFI 規(guī)范中,抽象了 PCI 的系統(tǒng)架構(gòu),典型的桌面系統(tǒng)的 PCI 架構(gòu)如圖 7-1 所示。
圖 7-1 單 PCI Root Bridge 的桌面系統(tǒng)
一般的桌面系統(tǒng)只有一個 PCI Host Bus(PCI 主機(jī)總線),用于完成 CPU 與 PCI 設(shè)備之間的數(shù)據(jù)交換。PCI Root Bridge(PCI 根橋)一般也只有一個,它管理一個局部總線,下掛一棵 PCI 總線樹。我們所要訪問的 PCI 設(shè)備,就掛在這棵總線樹上,它們屬于同一總線空間,如圖 7-2 所示。
從圖 7-2 中可以看出,PCI 總線樹上包含 PCI 總線、PCI 橋和 PCI 設(shè)備。系統(tǒng)通過三段編碼的方式進(jìn)行編碼,即通過 Bus Number(總線號)、Device Number(設(shè)備號)和 Function Number(功能號)來編碼,這種編碼一般簡稱為 BDF 碼。BDF 碼在 BIOS 進(jìn)行 PCI 總線掃描和枚舉過程中確定,可以用來作為查找 PCI 設(shè)備的索引。
圖 7-2 PCI 總線樹
找到 PCI 設(shè)備后,如何確定此設(shè)備就是自己要找的設(shè)備呢?每個 PCI 設(shè)備,除了主總線橋外,都會實現(xiàn)配置空間(主總線橋可以有選擇地實現(xiàn)),而在配置空間中,包含了設(shè)備廠商用來標(biāo)志自身的 Vendor ID(供應(yīng)商 ID)和 Device ID(設(shè)備 ID)。通過比對 PCI 設(shè)備的供應(yīng)商 ID 和設(shè)備 ID,可以確定所找的設(shè)備是否為目標(biāo)設(shè)備。
以 X86 平臺為例,可通過 CONFIG_ADDR 寄存器(0xCF8)和 CONFIG_DATA 寄存器(0xCFC),以 BDF 碼的形式訪問 PCI 設(shè)備,以得到設(shè)備的配置空間。圖 7-3 所示為 PCI設(shè)備的基本配置空間。
圖 7-3 PCI 設(shè)備的配置空間
PCI 設(shè)備的基本配置空間由 64 字節(jié)組成,地址范圍為 0x00~0x3F,主要用來識別設(shè)備、定義主機(jī)訪問 PCI 卡的方式。從圖 7-3 中可以看出,最開始的兩個寄存器就是 Vendor ID 和 Device ID 寄存器,這是用來標(biāo)志設(shè)備自身的寄存器,由 PCISIG 協(xié)會分配。比如Intel 集成顯卡 HD620,其 Vendor ID 為 0x8086,而 Device ID 為 0x5917。
在配置空間中,從地址 0x10至 0x24,包含了 6個 BaseAddressRegiste(r基址寄存器)。這組寄存器被稱為 BAR,保存了 PCI 設(shè)備使用的地址空間的基地址,也即該設(shè)備在 PCI 總線域中的地址。每個 PCI 設(shè)備最多可以有 6 個基址空間,但多數(shù)設(shè)備不會使用這么多,筆者以前常用的南京沁恒的 CH366 芯片,只使用了第一個 BAR。
BAR 可尋址 IO 地址空間或者 Memory 地址空間,其最低位是只讀位,顯示了可以訪問哪種地址空間。值為 0 表示寄存器是 Memory 地址譯碼,值為 1 表示寄存器是 IO 地址譯碼,如圖 7-4 所示。
圖 7-4 基地址寄存器的位分配
那么如何訪問 PCI 設(shè)備內(nèi)的寄存器和其他資源?答案是通過 BAR 寄存器,加上內(nèi)部寄存器相對于 BAR 的偏移地址。芯片手冊中,會提供關(guān)于內(nèi)部資源的使用說明,以 CH366 的芯片為例,其內(nèi)部寄存器說明如圖 7-5 所示。
圖 7-5 CH366 寄存器說明(節(jié)選自《CH366 中文手冊》)
CH366 的第一個 BAR 可以使用,它是 IO 地址譯碼的,其他 BAR 在芯片中是無效的。圖 7-4 表明,第一個 BAR 加上偏移地址,就可以成為芯片內(nèi)部相應(yīng)功能的寄存器。至于這些內(nèi)部寄存器有什么作用,則需要詳細(xì)了解芯片手冊才能知道。
總結(jié)來說,訪問 PCI/PCIE 設(shè)備的過程如下。
1)掃描整個系統(tǒng)空間,通過 BDF獲取 PCI/PCIE設(shè)備的配置空間。同一總線域上(即存在一個主橋),PCIE一共支持 256個總線、32個設(shè)備、8個功能,也就是說總線號最大值為 255、設(shè)備號最大值為 31、功能號最大為 7。
2)讀取 PCI/PCIE設(shè)備配置空間中的 VendorID和 DeviceID,確定是否為需要訪問的設(shè)備。
3)找到 PCI/PCIE設(shè)備后,獲取其配置空間中的 BAR,參照芯片手冊,訪問設(shè)備的內(nèi)部寄存器和資源。
UEFI 中提供了兩個主要的模塊來支持 PCI 總線,一是 PCI Host Bridge(PCI 主橋)控制器驅(qū)動,另一個是 PCI 總線驅(qū)動。這兩個模塊是和特定的平臺硬件綁定的,在這種機(jī)制下,屏蔽了不同的 CPU 架構(gòu)差異,為軟件開發(fā)者提供了比較一致的 Protocol 接口。下一節(jié)詳細(xì)介紹訪問 PCI/PCIE 設(shè)備的 Protocol。
7.1.2支持訪問 PCI/PCIE設(shè)備的 Protocol
UEFI標(biāo)準(zhǔn)中提供了兩類訪問 PCI/PCIE設(shè)備的 Protocol—EFI_PCI_ROOT_BRIDGE_ IO_PROTOCOL 和 EFI_PCI_IO_PROTOCOL。前者為 PCI 根橋提供了抽象的 IO 功能,它由 PCI Host Bus Controller(PCI 主總線驅(qū)動器)產(chǎn)生,一般由 PCI/PCIE 總線驅(qū)動用來枚舉設(shè)備、獲得 Option ROM、分配 PCI 設(shè)備資源等;后者由 PCI/PCIE 總線驅(qū)動為 PCI/PCIE 設(shè)備產(chǎn)生,一般由 PCI/PCIE 設(shè)備驅(qū)動用來訪問 PCI/PCIE 設(shè)備的 IO 空間、Memory 空間和配置空間。
這兩種 Protocol 的使用方法如下。
1.使用 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 中提供了基本的訪問接口,包括訪問 IO 空間、Memory 空間和配置空間的接口。該 Protocol 主要由 PCI/PCIE 總線驅(qū)動使用,當(dāng)然, UEFI 應(yīng)用也可以使用它來遍歷 PCI/PCIE 設(shè)備。
該 Protocol 中還提供了 DMA 接口,以支持總線驅(qū)動訪問系統(tǒng)內(nèi)存。代碼清單 7-1 給出了 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 的函數(shù)接口。
代碼清單 7-1 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 函數(shù)接口
typedefstruct_EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL{EFI_HANDLEParentHandle;//Protocol的父句柄EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_POLL_IO_MEM PollMem; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_POLL_IO_MEM PollIo;EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS Mem; //讀寫Memory空間EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS Io; //讀寫IO空間EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS Pci; //讀寫配置空間EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_COPY_MEM CopyMem; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_MAP Map; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_UNMAP Unmap;EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ALLOCATE_BUFFER AllocateBuffer; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_FREE_BUFFER FreeBuffer; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_FLUSH Flush; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_GET_ATTRIBUTES GetAttributes; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_SET_ATTRIBUTES SetAttributes; EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_CONFIGURATION Con?guration;UINT32 SegmentNumber;}EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL;
從代碼清單 7-1 中可以看出,此 Protocol 提供了非常豐富的訪問接口。由于篇幅所限, 無法將所有接口都介紹清楚,這里主要介紹如何讀寫 PCI/PCIE 設(shè)備的 3 種空間。
從 7.1.1 節(jié)的介紹中我們知道,PCI/PCIE 設(shè)備能訪問的空間包括 Memory 空間、IO 空間和配置空間。對于這 3 種空間,每個 PCI/PCIE 設(shè)備必須實現(xiàn)配置空間,而 Memory 空間和 IO 空間的功能,則不一定實現(xiàn)。7.1.1 節(jié)介紹的 PCIE 芯片 CH366 就只支持 IO 空間的訪問。
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 提供了訪問 Memory 空間的接口 Mem、訪問 IO 空間的接口 Io 和訪問配置空間的接口 Pci。這 3 個接口的參數(shù)類型都是一樣的,均為EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS,詳見代碼清單 7-2。
代碼清單 7-2 訪問 IO 空間、Memory 空間和配置空間的接口
typedef struct {EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM Read; //讀數(shù)據(jù)EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM Write; //寫數(shù)據(jù)} EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS;typedef EFI_STATUS (EFIAPI *EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM) (IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *This, //實例IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH Width, //讀寫寬度IN UINT64 Address, //IO空間/Memory空間/配置空間的地址IN UINTN Count, //讀寫的數(shù)據(jù)個數(shù),單位為讀寫寬度WidthIN OUT VOID *Buffer //對讀操作,這是目的緩沖區(qū);對寫操作,這是要寫的數(shù)據(jù)緩沖區(qū));
訪問 3 種空間的接口都包含讀數(shù)據(jù)和寫數(shù)據(jù)兩種操作,并且使用了同樣的數(shù)據(jù)結(jié)構(gòu)EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM。在此結(jié)構(gòu)中,Width 是指讀寫寬度, 其值由枚舉類型 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH 給出,一般包括 8 位、16 位、32 位和 64 位幾種。
需要注意的是,參數(shù) Address 在訪問 IO 空間、Memory 空間和配置空間時,其含義是不同的。對配置空間而言,Address 由 BDF 地址和 Register 偏移決定,即總線號、設(shè)備號、功能號和 Register 共同給出的尋址用索引。這是一個 64 位長的參數(shù),一般使用宏 EFI_ PCI_ADDRESS 來組合 BDF 和 Register 偏移。在 EDK2 中,這個宏定義于頭文件 MdePkg IncludeProtocolPciRootBridgeIo.h 中,其內(nèi)容如下。
#de?ne EFI_PCI_ADDRESS(bus, dev, func, reg) (UINT64) ( (((UINTN) bus) << 24) | (((UINTN) dev) << 16) | (((UINTN) func) << 8) | (((UINTN) (reg)) < 256 ? ((UINTN) (reg)): (UINT64) (LShiftU64 ((UINT64) (reg), 32))))
對 IO 空間而言,參數(shù) Address 是指 PCI 設(shè)備 IO 空間的 IO 地址;對 Memory 空間而言,參數(shù) Address 是指 PCI 設(shè)備 Memory 空間的 Memory 地址。參考 4.2.1 節(jié)可知,IO 地址和 Memory 地址是由 BAR 和偏移決定的,每個地址的作用還需要查看對應(yīng)芯片的說明手冊。
2.使用 EFI_PCI_IO_PROTOCOL
在 PCI/PCIE 設(shè)備驅(qū)動中,一般使用 EFI_PCI_IO_PROTOCOL 來訪問設(shè)備的內(nèi)部資源, Protocol 掛載在 PCI/PCIE 控制器上,運行在 EFI 啟動服務(wù)環(huán)境中,對 PCI/PCIE 設(shè)備進(jìn)行Memory 空間和 IO 空間訪問。其函數(shù)接口如代碼清單 7-3 所示。
代碼清單 7-3 EFI_PCI_IO_PROTOCOL 函數(shù)接口
typedef struct _EFI_PCI_IO_PROTOCOL { EFI_PCI_IO_PROTOCOL_POLL_IO_MEM PollMem; EFI_PCI_IO_PROTOCOL_POLL_IO_MEM PollIo;EFI_PCI_IO_PROTOCOL_ACCESS Mem; //讀寫Memory空間EFI_PCI_IO_PROTOCOL_ACCESS Io; //讀寫IO空間EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Pci; //讀寫配置空間EFI_PCI_IO_PROTOCOL_COPY_MEM CopyMem; EFI_PCI_IO_PROTOCOL_MAP Map; EFI_PCI_IO_PROTOCOL_UNMAP Unmap;EFI_PCI_IO_PROTOCOL_ALLOCATE_BUFFER AllocateBuffer; EFI_PCI_IO_PROTOCOL_FREE_BUFFER FreeBuffer; EFI_PCI_IO_PROTOCOL_FLUSH Flush;EFI_PCI_IO_PROTOCOL_GET_LOCATION GetLocation; EFI_PCI_IO_PROTOCOL_ATTRIBUTES Attributes; EFI_PCI_IO_PROTOCOL_GET_BAR_ATTRIBUTES GetBarAttributes; EFI_PCI_IO_PROTOCOL_SET_BAR_ATTRIBUTES SetBarAttributes; UINT64 RomSize;VOID*RomImage;} EFI_PCI_IO_PROTOCOL;與EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 不 同,EFI_PCI_IO_PROTOCOL 在 處理訪問 PCI/PCIE 的 Memory 空間、IO 空間和配置空間時,使用了兩種類型來區(qū)分。其中, 訪問 Memory 空間和 IO 空間使用的類型是 EFI_PCI_IO_PROTOCOL_ACCESS,如代碼清單 7-4 所示。
代碼清單7-4訪問IO空間和Memory空間的接口
typedefstruct{EFI_PCI_IO_PROTOCOL_IO_MEM Read; //讀數(shù)據(jù)EFI_PCI_IO_PROTOCOL_IO_MEM Write; //寫數(shù)據(jù)} EFI_PCI_IO_PROTOCOL_ACCESS;typedef EFI_STATUS (EFIAPI *EFI_PCI_IO_PROTOCOL_IO_MEM) (IN EFI_PCI_IO_PROTOCOL *This, //EFI_PCI_IO_PROTOCOL實例IN EFI_PCI_IO_PROTOCOL_WIDTH Width, //讀寫寬度,8位、16位、32位、64位IN UINT8 BarIndex, //在配置空間中的BAR索引值IN UINT64 Offset, //偏移寄存器,用來進(jìn)行IO空間/Memory空間讀寫IN UINTN Count, //讀寫的數(shù)據(jù)個數(shù),單位為讀寫寬度WidthINOUTVOID*Buffer//對讀操作,這是目的緩沖區(qū);對寫操作,這是要寫的數(shù)據(jù)緩沖區(qū));
上述代碼中的參數(shù) This 指向的是與 PCI/PCIE 設(shè)備本身相關(guān)的 EFI_PCI_IO_PROTOCOL實例,因此,在訪問設(shè)備時比較直接,不需要通過 BDF 等方式給出設(shè)備的地址。
IO 空間讀寫使用的函數(shù)為 Io.Read() 和 Io.Write() ;Memory 空間讀寫使用的函數(shù)為Mem.Read() 和 Mem.Write()。讀寫數(shù)據(jù)的時候,所需要訪問的地址由 BarIndex 和 Offset 共同規(guī)定。圖 7-3 所示為 PCI/PCIE 設(shè)備的配置空間,從圖中可知,BAR 總共有6 個, BarIndex 值的范圍為 0 至 5。最終訪問的地址,等于 BarIndex 所指向的 BAR 加上 Offset。至于此地址的含義,仍舊得查看 PCI/PCIE 芯片廠家提供的說明手冊。
訪問配置空間使用的數(shù)據(jù)結(jié)構(gòu)為 EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS,其接口說明如代碼清單 7-5 所示。
代碼清單7-5訪問配置空間的接口
typedefstruct{EFI_PCI_IO_PROTOCOL_CONFIG Read; //讀數(shù)據(jù)EFI_PCI_IO_PROTOCOL_CONFIG Write; //寫數(shù)據(jù)} EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS;typedef EFI_STATUS (EFIAPI *EFI_PCI_IO_PROTOCOL_CONFIG) (IN EFI_PCI_IO_PROTOCOL *This, //EFI_PCI_IO_PROTOCOL實例IN EFI_PCI_IO_PROTOCOL_WIDTH Width, //讀寫寬度,8位、16位、32位、64位IN UINT32 Offset, //偏移,在配置空間內(nèi)的偏移地址IN UINTN Count, //讀寫的個數(shù),以Width為單位INOUTVOID*Buffer//對讀操作,這是目的緩沖區(qū);對寫操作,這是要寫的數(shù)據(jù)緩沖區(qū));
Pci.Read() 和 Pci.Write() 函數(shù)用來訪問 PCI/PCIE 設(shè)備的配置空間。參數(shù) Offset 用來指定在配置空間內(nèi)的偏移地址,比如 Offset=0x10 時,是指 BAR0 寄存器。
PCI 設(shè)備的基本配置空間是由 64 字節(jié)(0x00~0x3F)組成的,這是所有 PCI/PCIE 設(shè)備必須支持的。此外,PCI/PCIE 設(shè)備還擴(kuò)展了 0x40~0xFF 這段配置空間,主要用來存放于MSI 中斷機(jī)制和電源管理相關(guān)的 Capability 結(jié)構(gòu)。另外,PCIE 設(shè)備還支持 0x100~0xFFF 這段配置空間,這段配置空間用于存放 PCIE 設(shè)備獨有的 Capability 結(jié)構(gòu)。
這些配置空間的信息,都可以通過 EFI_PCI_IO_PROTOCOL 獲取。至于配置空間內(nèi)寄存器的具體含義,讀者可以參考 PCI 標(biāo)準(zhǔn)和 PCIE 標(biāo)準(zhǔn)進(jìn)行深入學(xué)習(xí)。
7.1.3訪問 PCI/PCIE設(shè)備示例
本節(jié)準(zhǔn)備了相應(yīng)的示例,演示如何使用 7.1.2 節(jié)介紹的兩種 Protocol 來遍歷系統(tǒng)內(nèi)的PCI/PCIE 設(shè)備。大多數(shù)的機(jī)器上,只存在一個 PCI 總線域(PCISegment),即一個主橋。因此,在使用 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 的時候,應(yīng)該只會找到一個實例。我們設(shè)計的程序,其主要功能如下。
使用 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL,通過 BDF,遍歷所有 PCI/PCIE設(shè)備,打印出設(shè)備的相關(guān)信息。
尋找所有的 EFI_PCI_IO_PROTOCOL 實例,直接訪問每個實例的配置空間,將其信息打印出來。
本節(jié)提供的示例程序位于隨書代碼的 RobinPkgApplicationsListPCIMsg 目錄下。示例
7-1 演示了如何獲取兩類 Protocol 的實例。
【示例 7-1】獲取 Protocol 的實例。
EFI_STATUS LocatePCIRootBridgeIO(void){EFI_STATUS Status;EFI_HANDLE *PciHandleBuffer = NULL;UINTN HandleIndex = 0;UINTN HandleCount = 0;//獲取PciRootBridgeIOProtocol的所有句柄Status = gBS->LocateHandleBuffer(ByProtocol, &gE?PciRootBridgeIoProtocolGuid, NULL,&HandleCount, &PciHandleBuffer);if (EFI_ERROR(Status)) return Status;Print(L"Find PCI Root Bridge I/O Protocol: %d ",HandleCount);//獲取PciRootBridgeIOProtocol實例for(HandleIndex=0;HandleIndexHandleProtocol( PciHandleBuffer[HandleIndex], &gE?PciRootBridgeIoProtocolGuid, (VOID**)&gPCIRootBridgeIO);if (EFI_ERROR(Status)) continue; elsereturn EFI_SUCCESS;}return Status;}EFI_STATUS LocatePCIIO(void){EFI_STATUS Status;EFI_HANDLE *PciHandleBuffer = NULL;UINTN HandleIndex = 0;UINTN HandleCount = 0;//獲取PciIoProtocol的所有句柄 Status = gBS->LocateHandleBuffer(ByProtocol, &gE?PciIoProtocolGuid, NULL,&HandleCount, &PciHandleBuffer);if (EFI_ERROR(Status)) return Status; //unsupport gPCIIO_Count = HandleCount;Print(L"Find PCI I/O Protocol: %d ",HandleCount);//獲取PciIoProtocol實例,并存儲在全局變量gPCIIOArray中for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++){Status = gBS->HandleProtocol( PciHandleBuffer[HandleIndex], &gE?PciIoProtocolGuid, (VOID**)&(gPCIIOArray[HandleIndex]));}return Status;}
示例 7-1 中提供了兩個函數(shù)—LocatePCIRootBridgeIO() 和 LocatePCIIO(),用來獲取需要測試的兩類 Protocol 的實例。獲取實例的方法在 3.5 節(jié)中已經(jīng)介紹過了,本節(jié)的例程用了同樣的方法。EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 的實例,在大部分辦公用的個人電腦中只存在一個,因此直接用全局指針變量 gPCIRootBridgeIO 存儲;而 EFI_PCI_IO_ PROTOCOL 的實例存在多個,一般有多少個 PCI/PCIE 設(shè)備,就存在多少個實例,因此使用全局指針數(shù)組 gPCIIOArray[256] 來存儲這些實例。
為遍歷全部的 PCI/PCIE 設(shè)備,可以使用 gPCIRootBridgeIO 和 BDF 碼,循環(huán)查找掛載總線上的設(shè)備,代碼如示例 7-2 所示。
【示例 7-2】使用 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 遍歷 PCI/PCIE 設(shè)備。
EFI_STATUS ListPCIMessage1(void){EFI_STATUS Status=EFI_SUCCESS; PCI_TYPE00 Pci;UINT16 i,j,k,count=0; for(k=0;k<=PCI_MAX_BUS;k++)for(i=0;i<=PCI_MAX_DEVICE;i++) for(j=0;j<=PCI_MAX_FUNC;j++){//判斷設(shè)備是否存在Status = PciDevicePresent(gPCIRootBridgeIO,&Pci, (UINT8)k,(UINT8)i,(UINT8)j);if (Status == EFI_SUCCESS) //找到了設(shè)備{++count;Print(L"%02d. Bus-%02x Dev-%02x Func-%02x: ", count,(UINT8)k,(UINT8)i,(UINT8)j);Print(L"VendorID-%x DeviceID-%x ClassCode-%x", Pci.Hdr.VendorId,Pci.Hdr.DeviceId,Pci.Hdr.ClassCode[0]);Print(L" ");}}return EFI_SUCCESS;}
從代碼中可以看出,函數(shù)使用了 3 個 for 循環(huán)調(diào)用函數(shù) PciDevicePresent(),依次尋找PCI/PCIE 設(shè)備是否存在。如果存在,則取出已經(jīng)讀取到的配置空間的數(shù)據(jù),將設(shè)備的一些信息打印出來。
使用 EFI_PCI_IO_PROTOCOL 遍歷設(shè)備則比較簡單,因為之前所得到的此 Protocol 的實例,就是為 PCI/PCIE 設(shè)備產(chǎn)生的,實際上相當(dāng)于找到了設(shè)備,只需要將設(shè)備的信息打印出來即可。相應(yīng)的代碼見示例 7-3 所示。
【示例 7-3】使用 EFI_PCI_IO_PROTOCOL 遍歷 PCI/PCIE 設(shè)備。
EFI_STATUS ListPCIMessage2(void){UINTN i,count=0;PCI_TYPE00 Pci;for(i=0;iPci.Read(gPCIIOArray[i],E?PciWidthUint32,0,sizeof (PCI_TYPE00) / sizeof (UINT32),&Pci);++count;Print(L"%02d. VendorID-%x DeviceID-%x ClassCode-%x", count,Pci.Hdr.VendorId,Pci.Hdr.DeviceId,Pci.Hdr.ClassCode[0]);Print(L" ");}return EFI_SUCCESS;}
本節(jié)所準(zhǔn)備的示例,主要是為了演示如何使用與 PCI/PCIE 相關(guān)的兩個 Protocol。代碼本身還有許多不完善的地方,比如對多個總線域情況的處理、內(nèi)存的釋放、Protocol 的關(guān)閉等,都沒有考慮。本書的代碼,包括本節(jié)的代碼在內(nèi),建議讀者只用來學(xué)習(xí)使用,如果想商用,則應(yīng)該在代碼中將所有情況考慮到。
可參照 2.1.3 節(jié)的方法,設(shè)置編譯的環(huán)境變量,并使用如下命令編譯程序:
C:UEFIWorkspaceedk2uild -p RobinPkgRobinPkg.dsc -m RobinPkgApplicationsListPCIMsg ListPCIMsg.inf -a X64
所編譯的程序最好在實際的機(jī)器上測試運行。筆者使用 2.2.2 節(jié)搭建的 QEMU 環(huán)境來運行編譯好的 64 位 UEFI 程序,程序運行的結(jié)果如圖 7-6 所示。
圖 7-6 測試 ListPCIMsg 程序
-
接口
+關(guān)注
關(guān)注
33文章
8669瀏覽量
151551 -
軟件
+關(guān)注
關(guān)注
69文章
4987瀏覽量
87825 -
代碼
+關(guān)注
關(guān)注
30文章
4809瀏覽量
68826 -
UEFI
+關(guān)注
關(guān)注
0文章
53瀏覽量
11863
原文標(biāo)題:《UEFI編程實踐》選載之訪問 PCI/PCIE 設(shè)備
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論