30. PCI 루트 브리지 찾은 후 시스템의 모든 PCI 기능 가져오기

해당 장에서는 시스템에서 사용할 수 있는 모든 PCI 장치를 보여주고자 합니다.

이 작업을 위해 UEFI 스펙에서 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL을 활용해야 한다. 해당 프로토콜은 시스템의 모든 PCI 루트 브리지에 설치된다. 이 루트 브리지 아래에서 PCI 장치에 엑세스하기 위한 다양한 기능을 제공한다. 예를 들어 도움을 통하여 모든 PCI 장치에 대한 PCI 장치 메모리, I/O 및 구성 공간을 읽을 수 있다.

아래의 프로토콜 구조를 보고 무엇을 할 수 있는지 예측할 수 있다.

typedef struct _EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL {
 EFI_HANDLE ParentHandle;
 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;
 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_ACCESS 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 Configuration;
 UINT32 SegmentNumber;
} EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL;

시스템에서 많은 PCI 루트 브리지가 있기 때문에 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 또한 많이 존재한다. 그래서 이를 LoacteHandleBuffer를 사용하여 해당 프로토콜이 있는 모든 핸들을 가져온 후 모든 핸들에서 OpenProtocol을 사용하여 반복해야 한다.

EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL 사용을 위해<Protocol/PciRootBridgeIo.h> 헤더를 추가하고 FreePool을 위해 <Library/MemoryAllocationLib.h>를 추가하겠다. 그리고 당연히 inf 파일에 프로토콜도 추가 해야한다.

특정 핸들에 대한 프로토콜을 얻기 위해서 OpenProtocol 함수를 사용하면 된다.

해당 함수의 마지막 파라미터인 Attributes에는 여러가지가 존재한다.

우리는 위의 속성 중 EFI_OPEN_PROTOCOL_GET_PROTOCOL을 사용할 것이며 나머지 속성들은 UEFI 스펙에서 자세히 알아볼 수 있다.

반복문에서 OpenProtocol 호출을 사용하고 발견된 모든 프로토콜에 대해 직접 만든 EFI_STATUS PrintRootBridge( EFI_PCI_ROOT_BRIDGE_IOPROTOCOL* PciRootBridgeIo) 함수를 호출한다.

이제 PrintRootBridge 함수에 대해서 작성하겠다. 먼저 PCI 루트 브리지에 사용 가능한 모든 bus를 가져와야 한다. 이를 위해 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL에서 Configuration() 함수를 사용할 수 있다.

또한 다음은 ACPI resource descriptors에 대한 중요한 정보이다. 즉, 해당 함수를 실행하여 얻을 수 있는 데이터이다.

따라서 두가지 유형의 ACPI resource descriptors에 대한 ACPI 스펙을 확인해야 한다.

  • QWORD Address Sapce Descriptor

  • End Tag Descriptor

QWROD address space descriptor는 아래의 ACPI 스펙에 정의되어 있다. https://uefi.org/specs/ACPI/6.4/06_Device_Configuration/Device_Configuration.html?#qword-address-space-descriptorarrow-up-right 그리고 아래의 파일에 EDKII에서의 구조가 나와있다. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Acpi10.harrow-up-right

End Tag descriptor는 아래의 ACPI 스펙에 정의되어 있다. https://uefi.org/specs/ACPI/6.4/06_Device_Configuration/Device_Configuration.html#end-tagarrow-up-right 그리고 아래의 파일에 EDKII에서의 구조가 나와있다. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/IndustryStandard/Acpi10.harrow-up-right

따라서 PciRootBridgeIo->COnfiguration 호출에서 EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR의 배열을 얻은 후 디스크립터 ACPI_END_TAG_DESCRIPTOR를 만날 때까지 반복해야 한다.

QWORD address space descriptor는 여러 리소스 유형 중 하나를 가질 수 있다.

지금은 ACPI_ADDRESS_SPACE_TYPE_BUS 유형을 사용할 것이며 얼마나 많은 PCI bus가 PCI 루트 브리지를 가지고 있는지 알아야 한다. 이를 토대로 함수를 만들면 아래와 같다.

PCI 루트 브리지에 사용 가능한 모든 bus를 알고 있으면 EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL.Pci.Read() 함수의 사용으로 장치에 대한 PCI 구성 공간을 읽기 위한 시도를 할 수 있다.

Parametersaddress는 아래와 같이 구성된다.

그래서 우리는 Bus/Device/Function/Register 값에서 Address 변수를 생성하는 간단한 함수를 작성한다.

가능한 모든 PCI 함수를 반복하여 모든 기능에 대해 PCI 구성 공간에서 헤더를 읽어보도록 하겠다.

PCI bus, device 및 function의 최대 값은 PCI 규격에 따라 결정된다. EDKII에는 아래와 같이 정의되어 있다.

ACPI와 마찬가지로 최신 PCI 스펙에는 이전 사양이 포함된다.

가능한 모든 PCI 함수에 대해 공통 PCI 구성 공간 헤더를 읽으려고 한다.

데이터를 가져온 후 VendorId 필드가 유효한지 확인한다. 0xffff와 같지 않으면 실제 PCI 기능이며 이 경우에는 정보를 출력한다.

아래는 Bus/Device/Func의 반복에 대한 코드이다.

빌드 후 실행하면 아래와 같은 결과가 나온다.

결과에 대한 확인은 UEFI Shell의 pci 명령어를 통해서 확인이 가능하다.

챕터를 끝내기 이전에 PciLib를 활용하여 PCI 구성 공간 레지스터에 엑세스도 가능하니 아래의 링크도 참고하자.

https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PciLib.harrow-up-right

Last updated