31. ShellLib/PrintLib 함수를 사용해 PCI Vendor/Device 정보 가져오기
이번 장에서는 ListPCI 유틸리티를 수정하여 PCI Vendor와 Device에 대한 정보를 확인한다. 여기서 출력할 정보는 UEFI Shell에서 pci 명령어을 통해서 확인할 수 없는 정보이다. pci 명령은 PCI class와 subclass 코드에 대한 정보만 표시하기 때문에 여기서 하는 작업은 유용하게 사용할 수 있다.
이 코드에서 DESCRIPTOR_STR_MAX_SIZE는 Vendor와 Device 설명의 최대 크기이다. VendorDesc와 DeviceDesc를 단순화하기 위해 정적 배열을 선언하고 모든 설명을 포함할 수 있을 만큼 충분한 크기의 배열 크기를 선택해야 한다.
ListPCI.c에 추가한다.
/**
Function to determine if a given filename exists.
@param[in] Name Path to test.
@retval EFI_SUCCESS The Path represents a file.
@retval EFI_NOT_FOUND The Path does not represent a file.
@retval other The path failed to open.
**/
EFI_STATUS
EFIAPI
ShellFileExists(
IN CONST CHAR16 *Name
);
파일을 확인하는 코드는 아래와 같이 간단하게 작성할 수 있다. 이 코드는 위에서 작성했던 FindPCIDevDescription 함수 안에 작성한다.
EFI_STATUS Status = ShellFileExists(L"pci.ids");
if (EFI_ERROR(Status))
{
Print(L"No file pci.ids: %r\n", Status);
return Status;
}
다음으로 파일을 읽기 위해서는 해당 파일을 열어야 한다. 파일을 읽는 코드는 아래와 같고 위에서 작성한 코드 밑에 작성한다.
SHELL_FILE_HANDLE FileHandle;
Status = ShellOpenFileByName(L"pci.ids",
&FileHandle,
EFI_FILE_MODE_READ,
0);
if (EFI_ERROR(Status))
{
Print(L"Can't open file pci.ids: %r\n", Status);
return Status;
}
파싱 프로세스에서는 pci.ids 파일의 크기가 필요하다. 따라서 ShellLib의 함수를 다시 사용한다.
/**
Retrieve the size of a file.
This function extracts the file size info from the FileHandle's EFI_FILE_INFO
data.
@param[in] FileHandle The file handle from which size is retrieved.
@param[out] Size The pointer to size.
@retval EFI_SUCCESS The operation was completed sucessfully.
@retval EFI_DEVICE_ERROR Cannot access the file.
**/
EFI_STATUS
EFIAPI
ShellGetFileSize (
IN SHELL_FILE_HANDLE FileHandle,
OUT UINT64 *Size
);
파싱 프로세스에서 많은 문제가 발생할 수 있지만 열려 있는 파일 핸들을 닫아야 하는 경우에는 상관없다. 함수의 모든 지점에서 특정 위치로 이동하는 가장 쉬운 방법은 goto문을 사용하는 것이다. goto문을 사용하는 것은 문제가 많다고 하지만 어떤 상황에서는 코드를 다루기 쉽다. 리눅스 커널 코드에서도 정리 목적으로 많이 사용되기 때문에 여기서도 사용한다.
EFI_STATUS FindPCIDevDescription(IN UINT16 VendorId,
IN UINT16 DeviceId,
OUT CHAR16* VendorDesc,
OUT CHAR16* DeviceDesc,
IN UINTN DescBufferSize)
{
BOOLEAN Vendor_found = FALSE;
BOOLEAN Device_found = FALSE;
...
UINT64 FileSize;
Status = ShellGetFileSize(FileHandle, &FileSize);
if (EFI_ERROR(Status))
{
Print(L"Can't get file size for file pci.ids: %r\n", Status);
goto end;
}
...
end:
if (!Vendor_found) {
UnicodeSPrint(VendorDesc, DescBufferSize, L"Undefined");
}
if (!Device_found) {
UnicodeSPrint(DeviceDesc, DescBufferSize, L"Undefined");
}
ShellCloseFile(&FileHandle);
return Status;
}
데이터베이스 파일을 파싱할 때는 문자를 비교하기 때문에 Vendor나 Device에 대한 UINT16 값 코드를 16진수 문자열로 변환해야 한다.
/**
Converts a decimal value to a Null-terminated Ascii string.
Converts the decimal number specified by Value to a Null-terminated Ascii
string specified by Buffer containing at most Width characters. No padding of
spaces is ever performed.
...
If RADIX_HEX is set in Flags, then the output buffer will be formatted in
hexadecimal format.
...
If PREFIX_ZERO is set in Flags and PREFIX_ZERO is not being ignored, then
Buffer is padded with '0' characters so the combination of the optional '-'
sign character, '0' characters, digit characters for Value, and the
Null-terminator add up to Width characters.
If an error would be returned, then the function will ASSERT().
@param Buffer The pointer to the output buffer for the produced
Null-terminated Ascii string.
@param BufferSize The size of Buffer in bytes, including the
Null-terminator.
@param Flags The bitmask of flags that specify left justification,
zero pad, and commas.
@param Value The 64-bit signed value to convert to a string.
@param Width The maximum number of Ascii characters to place in
Buffer, not including the Null-terminator.
@retval RETURN_SUCCESS The decimal value is converted.
@retval RETURN_BUFFER_TOO_SMALL If BufferSize cannot hold the converted
value.
@retval RETURN_INVALID_PARAMETER If Buffer is NULL.
If PcdMaximumAsciiStringLength is not
zero, and BufferSize is greater than
PcdMaximumAsciiStringLength.
If unsupported bits are set in Flags.
If both COMMA_TYPE and RADIX_HEX are set in
Flags.
If Width >= MAXIMUM_VALUE_CHARACTERS.
**/
RETURN_STATUS
EFIAPI
AsciiValueToStringS (
IN OUT CHAR8 *Buffer,
IN UINTN BufferSize,
IN UINTN Flags,
IN INT64 Value,
IN UINTN Width
);
AsciiValueToStringS 함수를 사용할 때는 충분히 큰 배열을 만들고 올바른 플래그(RADIX_HEX | PREFIX_ZERO)를 사용한다.
마찬가지로 위에서 만든 함수 안에 작성한다.
Vendor가 검색되지 않을 경우 해당 패턴을 검색하고, 발견된 경우 Device 패턴을 검색한다.
StrStart와 StrEnd는 모두 다른 을 가리키며, 둘 사이의 정보가 필요로 하는 정보인지 생각해봐야 한다.
찾고 있는 최소한의 형식은 아래와 같다.
// 0123456 7
//\nVVVV |<desc>|\n
즉, StrStart가 (i+0)에서 을 가리키면 StrEnd는 Vendor 설명이 심볼(i+1-i+4)로 이루어지므로 최소 7( i+7)에서 을 가리키고, 그 다음에는 정확히 두 개의 공백이 존재해야 한다. 그래서 실제 설명이 비어 있더라도 문자열에 최소 8개의 심볼이 있어야 한다.
/**
Produces a Null-terminated Unicode string in an output buffer based on a Null-terminated
ASCII format string and variable argument list.
This function is similar as snprintf_s defined in C11.
Produces a Null-terminated Unicode string in the output buffer specified by StartOfBuffer
and BufferSize.
The Unicode string is produced by parsing the format string specified by FormatString.
Arguments are pulled from the variable argument list based on the contents of the
format string.
...
@param StartOfBuffer A pointer to the output buffer for the produced Null-terminated
Unicode string.
@param BufferSize The size, in bytes, of the output buffer specified by StartOfBuffer.
@param FormatString A Null-terminated ASCII format string.
@param ... Variable argument list whose contents are accessed based on the
format string specified by FormatString.
@return The number of Unicode characters in the produced output buffer not including the
Null-terminator.
**/
UINTN
EFIAPI
UnicodeSPrintAsciiFormat (
OUT CHAR16 *StartOfBuffer,
IN UINTN BufferSize,
IN CONST CHAR8 *FormatString,
...
);
위에서 ShellLib에 있는 함수와 PrintLib에 있는 함수를 사용하였기 때문에 해당 헤더 파일을 포함해야 한다.