目錄
7.1訪問PCI/PCIE 設備
7.1.1與PCI/PCIE設備通信的機制
7.1.2支持訪問PCI/PCIE設備的Protocol
7.1.3訪問PCI/PCIE設備示例
進行項目開發、構建產品框架的時候,最開始需要考慮的就是采用哪種通信方式讓軟件可以訪問外部設備(簡稱外設)。計算機經過多年的發展,提供了非常豐富的通信協議供程序員選擇。從古老的串口協議,到使用廣泛的 PCI/PCIE 協議,再到現在無處不在的 USB 協議等,可供選擇的方式實在太多。
在 Legacy BIOS 下,第三方開發者編寫訪問外設的代碼是件非常辛苦的事情。主要原因在于,Legacy BIOS 對很多協議支持得并不好。以筆者常用的 SMBus 協議為例,直到現在,也沒有 BIOS 廠家提供標準的中斷接口。大多數時候,只能通過閱讀主板芯片組規格書,了解與 SMBus 協議相關的寄存器信息,再根據標準的 SMBus 總線讀寫協議編寫代碼。市場上主板芯片組太多,這種方法寫出來的代碼,兼容性、穩定性都不理想,并且工作量非常大。
UEFI BIOS 的出現,解決了上述這些問題。從 UEFI 標準和 EDK2 的源碼也可以看出,常用的總線協議如 PCI/PCIE、SMBus、串口等,UEFI 都已經提供了支持。對于依賴于 BIOS 接口進行產品開發的廠商來說,這是一個非常好的消息,產品的開發速度將大幅提高,穩定性和兼容性也能得到保障。
本章將介紹如何在 UEFI 環境下使用 UEFI 規范提供的接口(即各類 Protocol),通過 PCI/PCIE、SMBus 和串口訪問外設。
7.1 訪問 PCI/PCIE 設備
PCI(Peripheral Component Interconnect)是一種高速的局部總線。其主要目的是連接周邊設備,將低速的設備與高速的處理器結合起來,以解決用戶對數據傳輸速率越來越高的要求。PCIE 總線是在 PCI 總線上繼承發展來的,其將信號傳輸方式從并行改為了串行, 傳輸速率也突飛猛進,PCI 的理論帶寬為 133MB/s,而 PCIE4.0 x16 的帶寬達到了 64GB/s。
從硬件結構角度看,PCI 和 PCIE 有很大的不同。PCI 總線采用并行總線結構,而 PCIE 總線使用了高速差分總線結構,使用端到端的連接方式,這使得兩者采用的拓撲結構差異較大。隨著技術的發展,目前市場上 PCI 設備越來越少,很多主板現在只提供 PCIE 的接口了。
這些差異對在 UEFI 下進行編程影響不大。UEFI 系統已經屏蔽了這些差異,提供了一致的訪問接口,下面詳細介紹如何訪問 PCI/PCIE 設備。
7.1.1與 PCI/PCIE設備通信的機制
PCI 協議和 PCIE 協議經過多年的發展,其內容已經非常龐大。本書主要論述的是如何在 UEFI 下進行編程。站在軟件工程師的角度,在訪問 PCI/PCIE 設備時,實際上只要回答以下兩個問題就可以了。
?如何在系統中找到需要訪問的設備?
?找到設備后,如何訪問設備內的寄存器或其他資源?
UEFI 規范中,抽象了 PCI 的系統架構,典型的桌面系統的 PCI 架構如圖 7-1 所示。
圖 7-1 單 PCI Root Bridge 的桌面系統
一般的桌面系統只有一個 PCI Host Bus(PCI 主機總線),用于完成 CPU 與 PCI 設備之間的數據交換。PCI Root Bridge(PCI 根橋)一般也只有一個,它管理一個局部總線,下掛一棵 PCI 總線樹。我們所要訪問的 PCI 設備,就掛在這棵總線樹上,它們屬于同一總線空間,如圖 7-2 所示。
從圖 7-2 中可以看出,PCI 總線樹上包含 PCI 總線、PCI 橋和 PCI 設備。系統通過三段編碼的方式進行編碼,即通過 Bus Number(總線號)、Device Number(設備號)和 Function Number(功能號)來編碼,這種編碼一般簡稱為 BDF 碼。BDF 碼在 BIOS 進行 PCI 總線掃描和枚舉過程中確定,可以用來作為查找 PCI 設備的索引。
圖 7-2 PCI 總線樹
找到 PCI 設備后,如何確定此設備就是自己要找的設備呢?每個 PCI 設備,除了主總線橋外,都會實現配置空間(主總線橋可以有選擇地實現),而在配置空間中,包含了設備廠商用來標志自身的 Vendor ID(供應商 ID)和 Device ID(設備 ID)。通過比對 PCI 設備的供應商 ID 和設備 ID,可以確定所找的設備是否為目標設備。
以 X86 平臺為例,可通過 CONFIG_ADDR 寄存器(0xCF8)和 CONFIG_DATA 寄存器(0xCFC),以 BDF 碼的形式訪問 PCI 設備,以得到設備的配置空間。圖 7-3 所示為 PCI設備的基本配置空間。
圖 7-3 PCI 設備的配置空間
PCI 設備的基本配置空間由 64 字節組成,地址范圍為 0x00~0x3F,主要用來識別設備、定義主機訪問 PCI 卡的方式。從圖 7-3 中可以看出,最開始的兩個寄存器就是 Vendor ID 和 Device ID 寄存器,這是用來標志設備自身的寄存器,由 PCISIG 協會分配。比如Intel 集成顯卡 HD620,其 Vendor ID 為 0x8086,而 Device ID 為 0x5917。
在配置空間中,從地址 0x10至 0x24,包含了 6個 BaseAddressRegiste(r基址寄存器)。這組寄存器被稱為 BAR,保存了 PCI 設備使用的地址空間的基地址,也即該設備在 PCI 總線域中的地址。每個 PCI 設備最多可以有 6 個基址空間,但多數設備不會使用這么多,筆者以前常用的南京沁恒的 CH366 芯片,只使用了第一個 BAR。
BAR 可尋址 IO 地址空間或者 Memory 地址空間,其最低位是只讀位,顯示了可以訪問哪種地址空間。值為 0 表示寄存器是 Memory 地址譯碼,值為 1 表示寄存器是 IO 地址譯碼,如圖 7-4 所示。
圖 7-4 基地址寄存器的位分配
那么如何訪問 PCI 設備內的寄存器和其他資源?答案是通過 BAR 寄存器,加上內部寄存器相對于 BAR 的偏移地址。芯片手冊中,會提供關于內部資源的使用說明,以 CH366 的芯片為例,其內部寄存器說明如圖 7-5 所示。
圖 7-5 CH366 寄存器說明(節選自《CH366 中文手冊》)
CH366 的第一個 BAR 可以使用,它是 IO 地址譯碼的,其他 BAR 在芯片中是無效的。圖 7-4 表明,第一個 BAR 加上偏移地址,就可以成為芯片內部相應功能的寄存器。至于這些內部寄存器有什么作用,則需要詳細了解芯片手冊才能知道。
總結來說,訪問 PCI/PCIE 設備的過程如下。
1)掃描整個系統空間,通過 BDF獲取 PCI/PCIE設備的配置空間。同一總線域上(即存在一個主橋),PCIE一共支持 256個總線、32個設備、8個功能,也就是說總線號最大值為 255、設備號最大值為 31、功能號最大為 7。
2)讀取 PCI/PCIE設備配置空間中的 VendorID和 DeviceID,確定是否為需要訪問的設備。
3)找到 PCI/PCIE設備后,獲取其配置空間中的 BAR,參照芯片手冊,訪問設備的內部寄存器和資源。
UEFI 中提供了兩個主要的模塊來支持 PCI 總線,一是 PCI Host Bridge(PCI 主橋)控制器驅動,另一個是 PCI 總線驅動。這兩個模塊是和特定的平臺硬件綁定的,在這種機制下,屏蔽了不同的 CPU 架構差異,為軟件開發者提供了比較一致的 Protocol 接口。下一節詳細介紹訪問 PCI/PCIE 設備的 Protocol。
7.1.2支持訪問 PCI/PCIE設備的 Protocol
UEFI標準中提供了兩類訪問 PCI/PCIE設備的 Protocol—EFI_PCI_ROOT_BRIDGE_ IO_PROTOCOL 和 EFI_PCI_IO_PROTOCOL。前者為 PCI 根橋提供了抽象的 IO 功能,它由 PCI Host Bus Controller(PCI 主總線驅動器)產生,一般由 PCI/PCIE 總線驅動用來枚舉設備、獲得 Option ROM、分配 PCI 設備資源等;后者由 PCI/PCIE 總線驅動為 PCI/PCIE 設備產生,一般由 PCI/PCIE 設備驅動用來訪問 PCI/PCIE 設備的 IO 空間、Memory 空間和配置空間。
這兩種 Protocol 的使用方法如下。
1.使用 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 中提供了基本的訪問接口,包括訪問 IO 空間、Memory 空間和配置空間的接口。該 Protocol 主要由 PCI/PCIE 總線驅動使用,當然, UEFI 應用也可以使用它來遍歷 PCI/PCIE 設備。
該 Protocol 中還提供了 DMA 接口,以支持總線驅動訪問系統內存。代碼清單 7-1 給出了 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 的函數接口。
代碼清單 7-1 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 函數接口
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 設備的 3 種空間。
從 7.1.1 節的介紹中我們知道,PCI/PCIE 設備能訪問的空間包括 Memory 空間、IO 空間和配置空間。對于這 3 種空間,每個 PCI/PCIE 設備必須實現配置空間,而 Memory 空間和 IO 空間的功能,則不一定實現。7.1.1 節介紹的 PCIE 芯片 CH366 就只支持 IO 空間的訪問。
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 提供了訪問 Memory 空間的接口 Mem、訪問 IO 空間的接口 Io 和訪問配置空間的接口 Pci。這 3 個接口的參數類型都是一樣的,均為EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS,詳見代碼清單 7-2。
代碼清單 7-2 訪問 IO 空間、Memory 空間和配置空間的接口
typedef struct {EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM Read; //讀數據EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM Write; //寫數據} 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, //讀寫的數據個數,單位為讀寫寬度WidthIN OUT VOID *Buffer //對讀操作,這是目的緩沖區;對寫操作,這是要寫的數據緩沖區);
訪問 3 種空間的接口都包含讀數據和寫數據兩種操作,并且使用了同樣的數據結構EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_IO_MEM。在此結構中,Width 是指讀寫寬度, 其值由枚舉類型 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH 給出,一般包括 8 位、16 位、32 位和 64 位幾種。
需要注意的是,參數 Address 在訪問 IO 空間、Memory 空間和配置空間時,其含義是不同的。對配置空間而言,Address 由 BDF 地址和 Register 偏移決定,即總線號、設備號、功能號和 Register 共同給出的尋址用索引。這是一個 64 位長的參數,一般使用宏 EFI_ PCI_ADDRESS 來組合 BDF 和 Register 偏移。在 EDK2 中,這個宏定義于頭文件 MdePkg IncludeProtocolPciRootBridgeIo.h 中,其內容如下。
#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 空間而言,參數 Address 是指 PCI 設備 IO 空間的 IO 地址;對 Memory 空間而言,參數 Address 是指 PCI 設備 Memory 空間的 Memory 地址。參考 4.2.1 節可知,IO 地址和 Memory 地址是由 BAR 和偏移決定的,每個地址的作用還需要查看對應芯片的說明手冊。
2.使用 EFI_PCI_IO_PROTOCOL
在 PCI/PCIE 設備驅動中,一般使用 EFI_PCI_IO_PROTOCOL 來訪問設備的內部資源, Protocol 掛載在 PCI/PCIE 控制器上,運行在 EFI 啟動服務環境中,對 PCI/PCIE 設備進行Memory 空間和 IO 空間訪問。其函數接口如代碼清單 7-3 所示。
代碼清單 7-3 EFI_PCI_IO_PROTOCOL 函數接口
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 空間和配置空間時,使用了兩種類型來區分。其中, 訪問 Memory 空間和 IO 空間使用的類型是 EFI_PCI_IO_PROTOCOL_ACCESS,如代碼清單 7-4 所示。
代碼清單7-4訪問IO空間和Memory空間的接口
typedefstruct{EFI_PCI_IO_PROTOCOL_IO_MEM Read; //讀數據EFI_PCI_IO_PROTOCOL_IO_MEM Write; //寫數據} 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, //偏移寄存器,用來進行IO空間/Memory空間讀寫IN UINTN Count, //讀寫的數據個數,單位為讀寫寬度WidthINOUTVOID*Buffer//對讀操作,這是目的緩沖區;對寫操作,這是要寫的數據緩沖區);
上述代碼中的參數 This 指向的是與 PCI/PCIE 設備本身相關的 EFI_PCI_IO_PROTOCOL實例,因此,在訪問設備時比較直接,不需要通過 BDF 等方式給出設備的地址。
IO 空間讀寫使用的函數為 Io.Read() 和 Io.Write() ;Memory 空間讀寫使用的函數為Mem.Read() 和 Mem.Write()。讀寫數據的時候,所需要訪問的地址由 BarIndex 和 Offset 共同規定。圖 7-3 所示為 PCI/PCIE 設備的配置空間,從圖中可知,BAR 總共有6 個, BarIndex 值的范圍為 0 至 5。最終訪問的地址,等于 BarIndex 所指向的 BAR 加上 Offset。至于此地址的含義,仍舊得查看 PCI/PCIE 芯片廠家提供的說明手冊。
訪問配置空間使用的數據結構為 EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS,其接口說明如代碼清單 7-5 所示。
代碼清單7-5訪問配置空間的接口
typedefstruct{EFI_PCI_IO_PROTOCOL_CONFIG Read; //讀數據EFI_PCI_IO_PROTOCOL_CONFIG Write; //寫數據} 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, //偏移,在配置空間內的偏移地址IN UINTN Count, //讀寫的個數,以Width為單位INOUTVOID*Buffer//對讀操作,這是目的緩沖區;對寫操作,這是要寫的數據緩沖區);
Pci.Read() 和 Pci.Write() 函數用來訪問 PCI/PCIE 設備的配置空間。參數 Offset 用來指定在配置空間內的偏移地址,比如 Offset=0x10 時,是指 BAR0 寄存器。
PCI 設備的基本配置空間是由 64 字節(0x00~0x3F)組成的,這是所有 PCI/PCIE 設備必須支持的。此外,PCI/PCIE 設備還擴展了 0x40~0xFF 這段配置空間,主要用來存放于MSI 中斷機制和電源管理相關的 Capability 結構。另外,PCIE 設備還支持 0x100~0xFFF 這段配置空間,這段配置空間用于存放 PCIE 設備獨有的 Capability 結構。
這些配置空間的信息,都可以通過 EFI_PCI_IO_PROTOCOL 獲取。至于配置空間內寄存器的具體含義,讀者可以參考 PCI 標準和 PCIE 標準進行深入學習。
7.1.3訪問 PCI/PCIE設備示例
本節準備了相應的示例,演示如何使用 7.1.2 節介紹的兩種 Protocol 來遍歷系統內的PCI/PCIE 設備。大多數的機器上,只存在一個 PCI 總線域(PCISegment),即一個主橋。因此,在使用 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 的時候,應該只會找到一個實例。我們設計的程序,其主要功能如下。
使用 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL,通過 BDF,遍歷所有 PCI/PCIE設備,打印出設備的相關信息。
尋找所有的 EFI_PCI_IO_PROTOCOL 實例,直接訪問每個實例的配置空間,將其信息打印出來。
本節提供的示例程序位于隨書代碼的 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 中提供了兩個函數—LocatePCIRootBridgeIO() 和 LocatePCIIO(),用來獲取需要測試的兩類 Protocol 的實例。獲取實例的方法在 3.5 節中已經介紹過了,本節的例程用了同樣的方法。EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 的實例,在大部分辦公用的個人電腦中只存在一個,因此直接用全局指針變量 gPCIRootBridgeIO 存儲;而 EFI_PCI_IO_ PROTOCOL 的實例存在多個,一般有多少個 PCI/PCIE 設備,就存在多少個實例,因此使用全局指針數組 gPCIIOArray[256] 來存儲這些實例。
為遍歷全部的 PCI/PCIE 設備,可以使用 gPCIRootBridgeIO 和 BDF 碼,循環查找掛載總線上的設備,代碼如示例 7-2 所示。
【示例 7-2】使用 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 遍歷 PCI/PCIE 設備。
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++){//判斷設備是否存在Status = PciDevicePresent(gPCIRootBridgeIO,&Pci, (UINT8)k,(UINT8)i,(UINT8)j);if (Status == EFI_SUCCESS) //找到了設備{++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;}
從代碼中可以看出,函數使用了 3 個 for 循環調用函數 PciDevicePresent(),依次尋找PCI/PCIE 設備是否存在。如果存在,則取出已經讀取到的配置空間的數據,將設備的一些信息打印出來。
使用 EFI_PCI_IO_PROTOCOL 遍歷設備則比較簡單,因為之前所得到的此 Protocol 的實例,就是為 PCI/PCIE 設備產生的,實際上相當于找到了設備,只需要將設備的信息打印出來即可。相應的代碼見示例 7-3 所示。
【示例 7-3】使用 EFI_PCI_IO_PROTOCOL 遍歷 PCI/PCIE 設備。
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;}
本節所準備的示例,主要是為了演示如何使用與 PCI/PCIE 相關的兩個 Protocol。代碼本身還有許多不完善的地方,比如對多個總線域情況的處理、內存的釋放、Protocol 的關閉等,都沒有考慮。本書的代碼,包括本節的代碼在內,建議讀者只用來學習使用,如果想商用,則應該在代碼中將所有情況考慮到。
可參照 2.1.3 節的方法,設置編譯的環境變量,并使用如下命令編譯程序:
C:UEFIWorkspaceedk2uild -p RobinPkgRobinPkg.dsc -m RobinPkgApplicationsListPCIMsg ListPCIMsg.inf -a X64
所編譯的程序最好在實際的機器上測試運行。筆者使用 2.2.2 節搭建的 QEMU 環境來運行編譯好的 64 位 UEFI 程序,程序運行的結果如圖 7-6 所示。
圖 7-6 測試 ListPCIMsg 程序
-
接口
+關注
關注
33文章
8694瀏覽量
151931 -
軟件
+關注
關注
69文章
5013瀏覽量
88089 -
代碼
+關注
關注
30文章
4828瀏覽量
69063 -
UEFI
+關注
關注
0文章
53瀏覽量
11871
原文標題:《UEFI編程實踐》選載之訪問 PCI/PCIE 設備
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
UEFI有什么定義?
如何切換BIOS啟動與UEFI啟動 bios與uefi切換方法
Embedded SIG | 樹莓派的UEFI支持和網絡啟動
基于UEFI固件的操作系統完整性度量機制
![基于<b class='flag-5'>UEFI</b>固件的操作系統完整性度量機制](https://file.elecfans.com/web2/M00/49/6E/poYBAGKhwLKAa6MOAAAfgH1-noc030.jpg)
基于UEFI固件的攻擊檢測系統的設計與實現
![基于<b class='flag-5'>UEFI</b>固件的攻擊檢測系統的設計與實現](https://file.elecfans.com/web2/M00/49/6E/poYBAGKhwLKAW5uxAAAPMvq1ZSk393.jpg)
uefi 嵌入式Linux,面向嵌入式平臺的高級UEFI開發環境.PDF
![<b class='flag-5'>uefi</b> 嵌入式Linux,面向嵌入式平臺的高級<b class='flag-5'>UEFI</b>開發<b class='flag-5'>環境</b>.PDF](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
龍芯LoongArch獲國際主流固件接口組織UEFI全面支持
![龍芯LoongArch獲國際主流固件<b class='flag-5'>接口</b>組織<b class='flag-5'>UEFI</b>全面支持](https://file.elecfans.com//web2/M00/72/4D/pYYBAGNRH4yADGnUAAPUWub5leo984.png)
研華SSD與Phoenix合作開發基于UEFI安全解決方案
![研華SSD與Phoenix合作開發基于<b class='flag-5'>UEFI</b>安全解決方案](https://file1.elecfans.com/web2/M00/A3/37/wKgaomT4EgaAW2gGAAE_gymWA9s480.jpg)
評論