31. ShellLib/PrintLib 함수를 사용해 PCI Vendor/Device 정보 가져오기

이번 장에서는 ListPCI 유틸리티를 수정하여 PCI Vendor와 Device에 대한 정보를 확인한다. 여기서 출력할 정보는 UEFI Shell에서 pci 명령어을 통해서 확인할 수 없는 정보이다. pci 명령은 PCI class와 subclass 코드에 대한 정보만 표시하기 때문에 여기서 하는 작업은 유용하게 사용할 수 있다.

pci 명령어 소스 코드 확인 링크

이번 장은 fpmurphy의 ShowPCIx(https://github.com/fpmurphy/UEFI-Utilities-2019/tree/master/MyApps/ShowPCIx)애플리케이션을 참고하여 작성하였다.

먼저, 이전에 만들었던 ListPCI.c에 코드를 기반으로 Vendor와 Device 설명을 채울 수 있는 FindPCIDevDescription 함수를 생성한다.

EFI_STATUS FindPCIDevDescription(IN UINT16 VendorId,
                                 IN UINT16 DeviceId,
                                 OUT CHAR16* VendorDesc,
                                 OUT CHAR16* DeviceDesc,
                                 IN UINTN DescBufferSize)

아래 코드는 메인 PCI loop인 PrintRootBridge 함수에 작성해야 한다.

if (PCIConfHdr.VendorId != 0xffff) {
  Print(L"  %02x:%02x.%02x - Vendor:%04x, Device:%04x",
                                                          Bus,
                                                          Device,
                                                          Func,
                                                          PCIConfHdr.VendorId,
                                                          PCIConfHdr.DeviceId);

  CHAR16 VendorDesc[DESCRIPTOR_STR_MAX_SIZE];
  CHAR16 DeviceDesc[DESCRIPTOR_STR_MAX_SIZE];
  Status = FindPCIDevDescription(PCIConfHdr.VendorId,
                                 PCIConfHdr.DeviceId,
                                 VendorDesc,
                                 DeviceDesc,
                                 DESCRIPTOR_STR_MAX_SIZE);
  if (!EFI_ERROR(Status)) {
    Print(L":    %s, %s\n", VendorDesc, DeviceDesc);
  } else {
    Print(L"\n");
  }
}

해당 함수의 전체 코드를 보여주면 아래와 같다.

이 코드에서 DESCRIPTOR_STR_MAX_SIZE는 Vendor와 Device 설명의 최대 크기이다. VendorDescDeviceDesc를 단순화하기 위해 정적 배열을 선언하고 모든 설명을 포함할 수 있을 만큼 충분한 크기의 배열 크기를 선택해야 한다. ListPCI.c에 추가한다.

이제 FindPCIDevDescription함수의 코드를 작성한다.

Public PCI ID Repository(https://pci-ids.ucw.cz/)에서 PCI Vendor와 Device 정보를 얻을 수 있다. 이 사이트에서는 공개된 PCI Vendor와 Devcie 조합이 포함된 pci.ids(https://pci-ids.ucw.cz/v2.2/pci.ids) 파일이 배포된다.

파일의 시작에는 정보가 표시되는 방법에 대한 설명이 있다.

필요한 pci.ids 파일을 QEMU 공유 폴더에 다운로드 받는다.

이제 함수를 작성한다.

먼저 PCI 데이터베이스 파일이 실제로 존재하는지 확인해야 한다.

해당 작업은 ShellLib의 함수를 활용할 수 있다. https://github.com/tianocore/edk2/blob/master/ShellPkg/Include/Library/ShellLib.h

파일을 확인하는 코드는 아래와 같이 간단하게 작성할 수 있다. 이 코드는 위에서 작성했던 FindPCIDevDescription 함수 안에 작성한다.

다음으로 파일을 읽기 위해서는 해당 파일을 열어야 한다. 파일을 읽는 코드는 아래와 같고 위에서 작성한 코드 밑에 작성한다.

파싱 프로세스에서는 pci.ids 파일의 크기가 필요하다. 따라서 ShellLib의 함수를 다시 사용한다.

파싱 프로세스에서 많은 문제가 발생할 수 있지만 열려 있는 파일 핸들을 닫아야 하는 경우에는 상관없다. 함수의 모든 지점에서 특정 위치로 이동하는 가장 쉬운 방법은 goto문을 사용하는 것이다. goto문을 사용하는 것은 문제가 많다고 하지만 어떤 상황에서는 코드를 다루기 쉽다. 리눅스 커널 코드에서도 정리 목적으로 많이 사용되기 때문에 여기서도 사용한다.

데이터베이스 파일을 파싱할 때는 문자를 비교하기 때문에 VendorDevice에 대한 UINT16 값 코드를 16진수 문자열로 변환해야 한다.

여기서는 AsciiValueToStringS를 사용할 수 있다. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/PrintLib.h

AsciiValueToStringS 함수를 사용할 때는 충분히 큰 배열을 만들고 올바른 플래그(RADIX_HEX | PREFIX_ZERO)를 사용한다. 마찬가지로 위에서 만든 함수 안에 작성한다.

추가적으로 AsciiValueToStringS는 16진수 값을 대문자로 저장하지만 데이터베이스 파일에서는 소문자로 저장되기 때문에 이를 변환해야 한다. 따라서 대소문자 변환을 위한 간단한 함수를 작성한다.

사용 방법은 아래와 같이 하면 된다.

이제 파싱 부분의 메인이다.

아래 내용은 위 코드에 대한 설명이다.

  • 파일을 블록 단위로 읽는다. (#define BLOCK_READ_SIZE (1024*4)) *ListPCI.c에 추가

  • 각 블록에서 기호를 검색하고 변수 StrStartStrEnd를 채우기 위해 실제 검색은 두 기호 사이의 데이터에 대해서 검색이 진행된다.

  • 각 블록 파싱이 끝난 후 파일 포인터를 마지막으로 발견된 (=StrEnd)에 설정한다. 이 작업을 위해 ShellLib의 다른 함수를 사용한다.

  • 파일의 끝에 도달했고 더이상 읽을 수 없을 경우 검색을 종료한다.

이제 위의 코드에서 <...>으로 있던 부분을 채워넣는다.

  • Vendor가 검색되지 않을 경우 해당 패턴을 검색하고, 발견된 경우 Device 패턴을 검색한다.

  • StrStartStrEnd는 모두 다른 을 가리키며, 둘 사이의 정보가 필요로 하는 정보인지 생각해봐야 한다.

  • 찾고 있는 최소한의 형식은 아래와 같다.

즉, StrStart가 (i+0)에서 을 가리키면 StrEndVendor 설명이 심볼(i+1-i+4)로 이루어지므로 최소 7( i+7)에서 을 가리키고, 그 다음에는 정확히 두 개의 공백이 존재해야 한다. 그래서 실제 설명이 비어 있더라도 문자열에 최소 8개의 심볼이 있어야 한다.

위에서 ShellLib에 있는 함수와 PrintLib에 있는 함수를 사용하였기 때문에 해당 헤더 파일을 포함해야 한다.

또한 ShellLib을 사용하기 위해 *.inf 파일에도 추가해야 한다.

빌드하고 ListPCI.efi 앱을 실행하면 아래와 같은 결과를 확인 수 있다.

Vendor:1234, Device:1111 는 QEMU VGA 컨트롤러이다. https://github.com/qemu/qemu/blob/master/docs/specs/standard-vga.txt

PCI expander bridges와 PCI root bridges를 QEMU에 추가

지금까지는 아래 명령으로 QEMU를 실행했다.

아래 명령으로 QEMU를 실행하면 다양한 PCI expender bridgesPCI root bridges를 추가할 수 있다.

위 명령으로 QEMU를 실행하고 ListPCI.efi 애플리케이션을 실행하면 아래와 같은 결과를 확인할 수 있다.

QEMU q35에서는 PCI-express root complexes를 추가할 수도 있다.

QEMU 파라미터에 대한 내용은 아래 링크를 통해 확인할 수 있다. https://blogs.oracle.com/linux/post/a-study-of-the-linux-kernel-pci-subsystem-with-qemu

UTF-8은 file 명령어로 확인했을 때 ASCII로 나오기 때문에 제외함.

Last updated