이 작업을 위해 UEFI 스펙에서 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL을 활용해야 한다.
해당 프로토콜은 시스템의 모든 PCI 루트 브리지에 설치된다. 이 루트 브리지 아래에서 PCI 장치에 엑세스하기 위한 다양한 기능을 제공한다. 예를 들어 도움을 통하여 모든 PCI 장치에 대한 PCI 장치 메모리, I/O 및 구성 공간을 읽을 수 있다.
시스템에서 많은 PCI 루트 브리지가 있기 때문에 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 또한 많이 존재한다. 그래서 이를 LoacteHandleBuffer를 사용하여 해당 프로토콜이 있는 모든 핸들을 가져온 후 모든 핸들에서 OpenProtocol을 사용하여 반복해야 한다.
FI_BOOT_SERVICES.LocateHandleBuffer()
Summary:
Returns an array of handles that support the requested protocol in a buffer allocated from pool.
Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_HANDLE_BUFFER) (
IN EFI_LOCATE_SEARCH_TYPE SearchType,
IN EFI_GUID *Protocol OPTIONAL,
IN VOID *SearchKey OPTIONAL,
OUT UINTN *NoHandles,
OUT EFI_HANDLE **Buffer
);
Parameters:
SearchType Specifies which handle(s) are to be returned.
Protocol Provides the protocol to search by. This parameter is only valid for a SearchType of ByProtocol.
SearchKey Supplies the search key depending on the SearchType.
NoHandles The number of handles returned in Buffer.
Buffer A pointer to the buffer to return the requested array of handles that support Protocol.
This buffer is allocated with a call to the Boot Service EFI_BOOT_SERVICES.AllocatePool().
It is the caller's responsibility to call the Boot Service EFI_BOOT_SERVICES.FreePool() when the caller no longer
requires the contents of Buffer.
Description:
The LocateHandleBuffer() function returns one or more handles that match the SearchType request. Buffer is allocated from pool, and the number of entries in Buffer is returned in NoHandles. Each
SearchType is described below:
AllHandles Protocol and SearchKey are ignored and the function returns an array of every handle in the system.
ByRegisterNotify SearchKey supplies the Registration returned by EFI_BOOT_SERVICES.RegisterProtocolNotify().
The function returns the next handle that is new for the Registration.
Only one handle is returned at a time, and the caller must loop until
no more handles are returned. Protocol is ignored for this search type.
ByProtocol All handles that support Protocol are returned. SearchKey is ignored for this search type.
EFI_STATUS Status;
UINTN HandleCount;
EFI_HANDLE *HandleBuffer;
Status = gBS->LocateHandleBuffer(
ByProtocol,
&gEfiPciRootBridgeIoProtocolGuid,
NULL,
&HandleCount,
&HandleBuffer
);
if (EFI_ERROR (Status)) {
Print(L"Can't locate EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL: %r\n", Status);
return Status;
}
Print(L"Number of PCI root bridges in the system: %d\n", HandleCount);
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL* PciRootBridgeIo;
for (UINTN Index = 0; Index < HandleCount; Index++) {
...
}
FreePool(HandleBuffer);
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 사용을 위해<Protocol/PciRootBridgeIo.h> 헤더를 추가하고 FreePool을 위해 <Library/MemoryAllocationLib.h>를 추가하겠다.
그리고 당연히 inf 파일에 프로토콜도 추가 해야한다.
[Protocols]
gEfiPciRootBridgeIoProtocolGuid
특정 핸들에 대한 프로토콜을 얻기 위해서 OpenProtocol 함수를 사용하면 된다.
EFI_BOOT_SERVICES.OpenProtocol()
Summary:
Queries a handle to determine if it supports a specified protocol. If the protocol is supported by the
handle, it opens the protocol on behalf of the calling agent. This is an extended version of the EFI boot
service EFI_BOOT_SERVICES.HandleProtocol().
Prototype
typedef
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL) (
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT VOID **Interface OPTIONAL,
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle,
IN UINT32 Attributes
);
Parameters:
Handle The handle for the protocol interface that is being opened.
Protocol The published unique identifier of the protocol.
Interface Supplies the address where a pointer to the corresponding Protocol Interface is returned. NULL will be returned in *Interface if a
structure is not associated with Protocol. This parameter is optional, and will be ignored if Attributes is EFI_OPEN_PROTOCOL_TEST_PROTOCOL.
AgentHandle The handle of the agent that is opening the protocol interface specified by Protocol and Interface. For agents that follow the UEFI
Driver Model, this parameter is the handle that contains the EFI_DRIVER_BINDING_PROTOCOL instance that is produced by
the UEFI driver that is opening the protocol interface. For UEFI applications, this is the image handle of the UEFI application that is
opening the protocol interface. For applications that use HandleProtocol() to open a protocol interface, this parameter is
the image handle of the EFI firmware.
ControllerHandle If the agent that is opening a protocol is a driver that follows the
UEFI Driver Model, then this parameter is the controller handle that
requires the protocol interface. If the agent does not follow the UEFI
Driver Model, then this parameter is optional and may be NULL.
Attributes The open mode of the protocol interface specified by Handle and
Protocol.
Description:
This function opens a protocol interface on the handle specified by Handle for the protocol specified by Protocol.
The first three parameters are the same as EFI_BOOT_SERVICES.HandleProtocol(). The only difference is that the agent that is opening a protocol interface is tracked in an EFI's internal handle
database
우리는 위의 속성 중 EFI_OPEN_PROTOCOL_GET_PROTOCOL을 사용할 것이며 나머지 속성들은 UEFI 스펙에서 자세히 알아볼 수 있다.
GET_PROTOCOL - 핸들에서 프로토콜 인터페이스를 가져오기 위해 드라이버에서 사용
반복문에서 OpenProtocol 호출을 사용하고 발견된 모든 프로토콜에 대해 직접 만든 EFI_STATUS PrintRootBridge( EFI_PCI_ROOT_BRIDGE_IOPROTOCOL* PciRootBridgeIo) 함수를 호출한다.
for (UINTN Index = 0; Index < HandleCount; Index++) {
Status = gBS->OpenProtocol (
HandleBuffer[Index],
&gEfiPciRootBridgeIoProtocolGuid,
(VOID **)&PciRootBridgeIo,
ImageHandle,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR(Status)) {
Print(L"Can't open protocol: %r\n", Status);
return Status;
}
Print(L"\nPCI Root Bridge %d\n", Index);
Status = PrintRootBridge(PciRootBridgeIo);
if (EFI_ERROR(Status)) {
Print(L"Error in PCI Root Bridge printing\n");
}
}
이제 PrintRootBridge 함수에 대해서 작성하겠다.
먼저 PCI 루트 브리지에 사용 가능한 모든 bus를 가져와야 한다. 이를 위해 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL에서 Configuration() 함수를 사용할 수 있다.
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Configuration()
Summary:
Retrieves the current resource settings of this PCI root bridge in the form of a set of ACPI resource descriptors.
Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_CONFIGURATION) (
IN EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *This,
OUT VOID **Resources
);
Parameters:
This A pointer to the EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.
Resources A pointer to the resource descriptors that describe the current configuration of this PCI root bridge.
The storage for the resource descriptors is allocated by this function. The caller must treat the return
buffer as read-only data, and the buffer must not be freed by the caller.
Description:
The Configuration() function retrieves a set of resource descriptors that contains the current
configuration of this PCI root bridge.
또한 다음은 ACPI resource descriptors에 대한 중요한 정보이다. 즉, 해당 함수를 실행하여 얻을 수 있는 데이터이다.
ACPI 규격에는 PCI 루트 브리지에 할당된 현재 리소스를 설명하는데 사용할 수 있는 두가지 resource descriptor 유형만 있다.
이들은 QWORD 주소 Space Descriptor와 End Tag 이다.
QWORD Address Space Descriptor는 동적 또는 고정 리소스에 대한 메모리, I/O 그리고 bus 번호 범위를 설명할 수 있다.
PCI 루트 브리지의 구성은 하나 이상의 QWORD Address Space Descriptors와 End Tag로 설명된다.
따라서 두가지 유형의 ACPI resource descriptors에 대한 ACPI 스펙을 확인해야 한다.
지금은 ACPI_ADDRESS_SPACE_TYPE_BUS 유형을 사용할 것이며 얼마나 많은 PCI bus가 PCI 루트 브리지를 가지고 있는지 알아야 한다. 이를 토대로 함수를 만들면 아래와 같다.
EFI_STATUS PrintRootBridge(EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL* PciRootBridgeIo)
{
EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR* AddressDescriptor;
EFI_STATUS Status = PciRootBridgeIo->Configuration(
PciRootBridgeIo,
(VOID**)&AddressDescriptor
);
if (EFI_ERROR(Status)) {
Print(L"\tError! Can't get EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR: %r\n", Status);
return Status;
}
while (AddressDescriptor->Desc != ACPI_END_TAG_DESCRIPTOR) {
if (AddressDescriptor->ResType == ACPI_ADDRESS_SPACE_TYPE_BUS) {
...
}
AddressDescriptor++;
}
}
return Status;
PCI 루트 브리지에 사용 가능한 모든 bus를 알고 있으면 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Pci.Read() 함수의 사용으로 장치에 대한 PCI 구성 공간을 읽기 위한 시도를 할 수 있다.
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Pci.Read()
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Pci.Write()
Summary:
Enables a PCI driver to access PCI controller registers in a PCI root bridge’s configuration space.
Prototype:
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,
IN UINTN Count,
IN OUT VOID *Buffer
);
Parameters:
This A pointer to the EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.
Width Signifies the width of the memory operations.
Address The address within the PCI configuration space for the PCI controller.
Count The number of PCI configuration operations to perform. Bytes moved is Width size * Count, starting at Address.
Buffer For read operations, the destination buffer to store the results.
For write operations, the source buffer to write data from.
Description:
The Pci.Read() and Pci.Write() functions enable a driver to access PCI configuration registers for a
PCI controller.
All the PCI transactions generated by this function are guaranteed to be completed before this function
returns.
Parameters의 address는 아래와 같이 구성된다.
그래서 우리는 Bus/Device/Function/Register 값에서 Address 변수를 생성하는 간단한 함수를 작성한다.