62. UEFI Device path의 구조

동적 및 정적 Device path. Device path를 통한 상호 작용

이번 장에서는 UEFI의 DevicePath 개념에 대해 설명한다.

Device path는 UEFI 환경에서 장치에 대한 프로그래밍 경로를 정의하는 데 사용한다.

내부적으로 Device Path는 메모리에서 서로를 따르고 있는 소위 Device Path nodes 로 구성되어 있다.

모든 Device Path의 맨 끝에는 특별한 End type의 Device Path node가 있다.

기본적으로 다음과 같이 생겼다.

Device path = 
  <Device Path node>
  ...
  <Device Path node>
  <Device Path node with the End type>

Device Path에서 소위 Device Path instances를 여러 개 인코딩할 가능성이 있다. 이 경우 장치 경로는 조금 더 복잡해진다.

Device path = 
  <Device Path node>
  ...
  <Device Path node>
  <Device Path node with the End instance type>
  <Device Path node>
  ...
  <Device Path node>
  <Device Path node with the End instance type>
  <Device Path node>
  ...
  <Device Path node>
  <Device Path node with the End type>

이제 <Device Path node>의 정의를 살펴보자. 각 <Device Path node>에는 EFI_DEVICE_PATH_PROTOCOL 헤더가 있다. 이 헤더의 크기는 4바이트에 불과하다. 그리고 헤더의 Type/SubType 필드 값에 의해 지시되는 형식의 데이터 바로 뒤에 오게 된다.

https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/DevicePath.h

#pragma pack(1)

typedef struct {
  UINT8    Type;    ///< 0x01 Hardware Device Path.
                    ///< 0x02 ACPI Device Path.
                    ///< 0x03 Messaging Device Path.
                    ///< 0x04 Media Device Path.
                    ///< 0x05 BIOS Boot Specification Device Path.
                    ///< 0x7F End of Hardware Device Path.

  UINT8    SubType; ///< Varies by Type

  UINT8    Length[2]; ///< Specific Device Path data. Type and Sub-Type define
                      ///< type of data. Size of data is included in Length.
} EFI_DEVICE_PATH_PROTOCOL;

현재 6가지 주요 Device path type이 있다. 위의 Type 필드의 주석에서 확인 가능하다. 이러한 각 type에는 여러 하위 type이 존재한다. 그리고 이러한 각 하위 type에는 해당 데이터에 대해 잘 정의된 고유한 형식이 있고 그리고 그들 모두 UEFI 스펙에 정의되어 있다.

https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/DevicePath.h에서 구조를 한 눈에 볼 수 있다. 이 파일에 있는 대부분의 구조체들은 Type/SubType 필드를 기반으로 하는 다양한 데이터 형식에 해당한다.

Length 필드는 헤더를 포함한 Device Node의 전체 크기를 정의한다. Length[0]은 하위 바이트를 정의하고 Length[1]은 상위 바이트를 정의한다.

예를 들어 PCI 장치에는 다음이 있다.

  • #define HARDWARE_DEVICE_PATH 0x01 유형

  • #define HW_PCI_DP 0x01 하위 유형

전체 경로는 다음과 같이 인코딩된다.

///
/// PCI Device Path.
///
typedef struct {
  EFI_DEVICE_PATH_PROTOCOL    Header;
  ///
  /// PCI Function Number.
  ///
  UINT8                       Function;
  ///
  /// PCI Device Number.
  ///
  UINT8                       Device;
} PCI_DEVICE_PATH;

EFI_DEVICE_PATH_PROTOCOL 구조를 풀면 다음과 같다.

{
  UINT8    Type      = HARDWARE_DEVICE_PATH = 0x01
  UINT8    SubType   = HW_PCI_DP = 0x01
  UINT8    Length[0] = 0x06
  UINT8    Length[1] = 0x00
  UINT8    Function;
  UINT8    Device;
}

Device Path/Device Node를 정적 및 동적으로 생성하기

UEFI에서 Device Path로 작업할 수 있는 방법을 조사하기 위해 애플리케이션을 만들어보자.

애플리케이션을 만들고 소스 코드에 <Library/DevicePathLib.h> 헤더를 포함시켜준다. 이것은 UEFI Device Path 라이브러리의 헤더이다.(https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Library/DevicePathLib.h)

PCI_DEVICE_PATH 유형의 단일 노드만으로 PCI 장치에 대한 Device Path를 생성한다고 가정해 보자.

이론적 경로는 PCI 장치의 Function=5/Device=3을 참조한다.

이러한 특성을 가진 정적 Device Node에 대한 코드는 다음과 같다.

#define EXAMPLE_PCI_FUNCTION 5
#define EXAMPLE_PCI_DEVICE 3

PCI_DEVICE_PATH PciDevicePathNodeStatic;
PciDevicePathNodeStatic.Header.Type = HARDWARE_DEVICE_PATH;
PciDevicePathNodeStatic.Header.SubType = HW_PCI_DP;
PciDevicePathNodeStatic.Header.Length[0] = sizeof(PCI_DEVICE_PATH);
PciDevicePathNodeStatic.Header.Length[1] = 0;
PciDevicePathNodeStatic.Function = EXAMPLE_PCI_FUNCTION;
PciDevicePathNodeStatic.Device = EXAMPLE_PCI_DEVICE;

또는 이와 같다.(사실상 기능은 동일하다.)

#define EXAMPLE_PCI_FUNCTION 5
#define EXAMPLE_PCI_DEVICE 3

PCI_DEVICE_PATH PciDevicePathNodeStatic = {
{
  {
    HARDWARE_DEVICE_PATH,
    HW_PCI_DP,
    {
      (UINT8) (sizeof (PCI_DEVICE_PATH)),
      (UINT8) ((sizeof (PCI_DEVICE_PATH)) >> 8)
    }
  },
  EXAMPLE_PCI_FUNCTION,
  EXAMPLE_PCI_DEVICE;
};

DevicePathLib에는 사람이 읽을 수 있는 텍스트 형식으로 DeviceNodes를 출력해주는 함수가 존재한다.

/**
  Converts a device node to its string representation.
  @param DeviceNode        A Pointer to the device node to be converted.
  @param DisplayOnly       If DisplayOnly is TRUE, then the shorter text representation
                           of the display node is used, where applicable. If DisplayOnly
                           is FALSE, then the longer text representation of the display node
                           is used.
  @param AllowShortcuts    If AllowShortcuts is TRUE, then the shortcut forms of text
                           representation for a device node can be used, where applicable.
  @return A pointer to the allocated text representation of the device node or NULL if DeviceNode
          is NULL or there was insufficient memory.
**/
CHAR16 *
EFIAPI
ConvertDeviceNodeToText (
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *DeviceNode,
  IN BOOLEAN                         DisplayOnly,
  IN BOOLEAN                         AllowShortcuts
  );

다음과 같이 Device Node를 출력하는데 사용하면,

Print(L"PciDevicePathNodeStatic: %s\n", ConvertDeviceNodeToText((EFI_DEVICE_PATH_PROTOCOL*) &PciDevicePathNodeStatic, FALSE, FALSE));

아래와 같은 출력을 얻을 수 있다.

FS0:\> DevicePath.efi
PciDevicePathNodeStatic: Pci(0x3,0x5)

이제 Device Path 노드를 동적으로 생성해보자. 이를 위해 라이브러리에서 CreateDeviceNode함수를 사용할 수 있다.

/**
  Creates a device node.
  This function creates a new device node in a newly allocated buffer of size
  NodeLength and initializes the device path node header with NodeType and NodeSubType.
  The new device path node is returned.
  If NodeLength is smaller than a device path header, then NULL is returned.
  If there is not enough memory to allocate space for the new device path, then
  NULL is returned.
  The memory is allocated from EFI boot services memory. It is the responsibility
  of the caller to free the memory allocated.
  @param  NodeType                   The device node type for the new device node.
  @param  NodeSubType                The device node sub-type for the new device node.
  @param  NodeLength                 The length of the new device node.
  @return The new device path.
**/
EFI_DEVICE_PATH_PROTOCOL *
CreateDeviceNode (
   UINT8                           NodeType,
   UINT8                           NodeSubType,
   UINT16                          NodeLength
  )

코드는 다음과 같다.

EFI_DEVICE_PATH_PROTOCOL* PciDevicePathNodeDynamic = CreateDeviceNode(HARDWARE_DEVICE_PATH, HW_PCI_DP, sizeof(PCI_DEVICE_PATH));
((PCI_DEVICE_PATH*)PciDevicePathNodeDynamic)->Function = EXAMPLE_PCI_FUNCTION;
((PCI_DEVICE_PATH*)PciDevicePathNodeDynamic)->Device = EXAMPLE_PCI_DEVICE;
Print(L"PciDevicePathNodeDynamic: %s\n", ConvertDeviceNodeToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathNodeDynamic, FALSE, FALSE));

그리고 이 코드는 우리에게 정확히 같은 결과를 출력해준다.

FS0:\> DevicePath.efi
PciDevicePathNodeStatic: Pci(0x3,0x5)
PciDevicePathNodeDynamic: Pci(0x3,0x5)

이제 완전한 Device Path를 생성해 보자. 이를 위해 생성한 현재 항목 바로 뒤에 End 유형의 Device Node를 추가해야 한다.

다시 한 번 정적 선언 방법으로 시작하자. PCI_DEVICE_PATH 노드와 End 노드를 모두 포함하는 자체 구조체 type을 만들어야한다.

#pragma pack(1)
typedef struct {
  PCI_DEVICE_PATH             PciDevicePath;
  EFI_DEVICE_PATH_PROTOCOL    End;
} FULL_PCI_DEVICE_PATH;
#pragma pack()


FULL_PCI_DEVICE_PATH  PciDevicePathStatic = {
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_PCI_DP,
      {
        (UINT8) (sizeof (PCI_DEVICE_PATH)),
        (UINT8) ((sizeof (PCI_DEVICE_PATH)) >> 8)
      }
    },
    EXAMPLE_PCI_FUNCTION,
    EXAMPLE_PCI_DEVICE
  },
  {
    END_DEVICE_PATH_TYPE,
    END_ENTIRE_DEVICE_PATH_SUBTYPE,
    {
      (UINT8) (END_DEVICE_PATH_LENGTH),
      (UINT8) ((END_DEVICE_PATH_LENGTH) >> 8)
    }
  }
};

Device Path를 문자열로 출력하려면 ConvertDevicePathToText 함수를 사용하면 된다.

이 함수는 ConvertDeviceNodeToText와 매우 유사하기 때문에 혼동하기 쉽다. Device Path가 아닌 Device Node에서 ConvertDevicePathToText를 수행하려고 하면 시스템이 중단될 수 있다.

/**
  Converts a device path to its text representation.
  @param DevicePath      A Pointer to the device to be converted.
  @param DisplayOnly     If DisplayOnly is TRUE, then the shorter text representation
                         of the display node is used, where applicable. If DisplayOnly
                         is FALSE, then the longer text representation of the display node
                         is used.
  @param AllowShortcuts  If AllowShortcuts is TRUE, then the shortcut forms of text
                         representation for a device node can be used, where applicable.
  @return A pointer to the allocated text representation of the device path or
          NULL if DeviceNode is NULL or there was insufficient memory.
**/
CHAR16 *
EFIAPI
ConvertDevicePathToText (
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *DevicePath,
  IN BOOLEAN                         DisplayOnly,
  IN BOOLEAN                         AllowShortcuts
  );

이전처럼 간단하게 사용할 수 있다.

Print(L"PciDevicePathStatic: %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) &PciDevicePathStatic, FALSE, FALSE));

실행해보면 다시 한 번 동일한 결과를 출력한다(중요한 문자열 표현이 없는 End 노드만 추가했기 때문).

PciDevicePathStatic: Pci(0x3,0x5)

마지막으로 Device Path를 동적으로 생성해보자. 이 작업을 위해 AppendDevicePathNode 함수를 활용하면 된다.

/**
  Creates a new path by appending the device node to the device path.
  This function creates a new device path by appending a copy of the device node specified by
  DevicePathNode to a copy of the device path specified by DevicePath in an allocated buffer.
  The end-of-device-path device node is moved after the end of the appended device node.
  If DevicePathNode is NULL then a copy of DevicePath is returned.
  If DevicePath is NULL then a copy of DevicePathNode, followed by an end-of-device path device
  node is returned.
  If both DevicePathNode and DevicePath are NULL then a copy of an end-of-device-path device node
  is returned.
  If there is not enough memory to allocate space for the new device path, then NULL is returned.
  The memory is allocated from EFI boot services memory. It is the responsibility of the caller to
  free the memory allocated.
  @param  DevicePath                 A pointer to a device path data structure.
  @param  DevicePathNode             A pointer to a single device path node.
  @retval NULL      There is not enough memory for the new device path.
  @retval Others    A pointer to the new device path if success.
                    A copy of DevicePathNode followed by an end-of-device-path node
                    if both FirstDevicePath and SecondDevicePath are NULL.
                    A copy of an end-of-device-path node if both FirstDevicePath and SecondDevicePath are NULL.
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
AppendDevicePathNode (
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *DevicePath      OPTIONAL,
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *DevicePathNode  OPTIONAL
  );

주석에서 알 수 있듯이 첫 번째 인수 대신 NULL을 제공하면 함수는 필요한 Device Node End를 Device Node에 추가한다. 따라서 Device Node에서 Device Path를 효과적으로 생성한다.

EFI_DEVICE_PATH_PROTOCOL* PciDevicePathDynamic = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)NULL, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);

출력하려는 경우 다음과 같은 코드를 쓸 수 있다.

Print(L"PciDevicePathDynamic: %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathDynamic, FALSE, FALSE));

또 한번 같은 결과를 출력한다.

PciDevicePathDynamic: Pci(0x3,0x5)

다중 노드 Device Path

UEFI Shell을 부팅할때 자세히 보면 매핑을 위한 Device Path를 보여주는 매핑 테이블이 출력되는 것을 알 수 있다.

UEFI Interactive Shell v2.2
EDK II
UEFI v2.70 (EDK II, 0x00010000)
Mapping table
      FS0: Alias(s):HD0a1:;BLK1:
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)
     BLK0: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
     BLK2: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
Press ESC in 5 seconds to skip startup.nsh or any other key to continue.

PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1) 경로에서 / 사이의 각 텍스트는 Device Path node에 해당한다. 예를 들어 이 특정 경로는 5개의 Device Node로 구성된다(Device End 노드를 잊으면 안된다).

PCI 장치의 정상적인 경로에는 Pci 노드 앞에 PciRoot 노드가 있다. 그러나 이러한 것들을 확인하는 것은 UEFI 스펙의 작업이 아니다. UEFI 스펙은 레슨 시작 부분에서 분석된 Device Path에 대한 구조만 정의해준다.

따라서 모든 Device node에서 Device Path를 생성할 수 있다.

이미 알고 있는 AppendDevicePathNode 함수를 사용해서 우리의 path에 3개의 PCI 노드를 더 추가해 보자.

PciDevicePathDynamic = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathDynamic, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);
PciDevicePathDynamic = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathDynamic, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);
PciDevicePathDynamic = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathDynamic, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);
Print(L"Complicated DevicePath (AppendDevicePathNode): %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathDynamic, FALSE, FALSE));

Print 문이 다음과 같이 출력할 것이라고 짐작할 수 있다.

Complicated DevicePath (AppendDevicePathNode): Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)

AppendDevicePath 함수를 사용하여 두 개의 Device Path를 연결할 수도 있다.

이 함수는 Device Path 데이터 노드를 올바르게 연결하므로 최종 경로에는 하나의 Device Path End 노드만 존재한다.

/**
  Creates a new device path by appending a second device path to a first device path.
  This function creates a new device path by appending a copy of SecondDevicePath to a copy of
  FirstDevicePath in a newly allocated buffer.  Only the end-of-device-path device node from
  SecondDevicePath is retained. The newly created device path is returned.
  If FirstDevicePath is NULL, then it is ignored, and a duplicate of SecondDevicePath is returned.
  If SecondDevicePath is NULL, then it is ignored, and a duplicate of FirstDevicePath is returned.
  If both FirstDevicePath and SecondDevicePath are NULL, then a copy of an end-of-device-path is
  returned.
  If there is not enough memory for the newly allocated buffer, then NULL is returned.
  The memory for the new device path is allocated from EFI boot services memory. It is the
  responsibility of the caller to free the memory allocated.
  @param  FirstDevicePath            A pointer to a device path data structure.
  @param  SecondDevicePath           A pointer to a device path data structure.
  @retval NULL      If there is not enough memory for the newly allocated buffer.
  @retval NULL      If FirstDevicePath or SecondDevicePath is invalid.
  @retval Others    A pointer to the new device path if success.
                    Or a copy an end-of-device-path if both FirstDevicePath and SecondDevicePath are NULL.
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
AppendDevicePath (
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *FirstDevicePath   OPTIONAL,
  IN CONST EFI_DEVICE_PATH_PROTOCOL  *SecondDevicePath  OPTIONAL
  );

우리의 경로에서 데이터 노드를 두 배로 늘리기 위해 이와 같이 사용할 수 있다.

PciDevicePathDynamic = AppendDevicePath((EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathDynamic, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathDynamic);
Print(L"Complicated DevicePath (AppendDevicePath): %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathDynamic, FALSE, FALSE));

그러면 4개가 아닌 8개의 PCI 노드가 제공된다.

Complicated DevicePath (AppendDevicePath): Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)/Pci(0x3,0x5)

AppendDevicePathNode/AppendDevicePath 함수의 본질을 잊으면 안된다. 모든 호출은 새 버퍼를 생성하지만 더 이상 필요하지 않은 이전 버퍼를 해제하는 것을 잊지 말아야한다. 그렇지 않으면 호출할 때마다 메모리 누수가 발생하게 된다. 따라서 더 정확한 코드는 다음과 같다.

EFI_DEVICE_PATH_PROTOCOL* PciDevicePathDynamicMulti;
EFI_DEVICE_PATH_PROTOCOL* TempPath;

PciDevicePathDynamicMulti = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathDynamic, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);
TempPath = PciDevicePathDynamicMulti;
PciDevicePathDynamicMulti = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)TempPath, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);
FreePool(TempPath);
TempPath = PciDevicePathDynamicMulti;
PciDevicePathDynamicMulti = AppendDevicePathNode((EFI_DEVICE_PATH_PROTOCOL*)TempPath, (EFI_DEVICE_PATH_PROTOCOL*)PciDevicePathNodeDynamic);
FreePool(TempPath);
Print(L"Complicated DevicePath (AppendDevicePathNode): %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathDynamicMulti, FALSE, FALSE));

TempPath = PciDevicePathDynamicMulti;
PciDevicePathDynamicMulti = AppendDevicePath((EFI_DEVICE_PATH_PROTOCOL*)TempPath, (EFI_DEVICE_PATH_PROTOCOL*)TempPath);
FreePool(TempPath);
Print(L"Complicated DevicePath (AppendDevicePath): %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathDynamicMulti, FALSE, FALSE));

Device Path 반복문

라이브러리는 Device Paths를 반복하는 여러 함수를 제공한다. Device Path를 반복하는 유일한 방법은 Device End 노드를 찾을 때까지 Device Node를 하나씩 살펴보는 것이므로 라이브러리는 IsDevicePathEndNextDevicePathNode라는 두 가지 함수를 제공한다.

/**
  Determines if a device path node is an end node of an entire device path.
  Determines if a device path node specified by Node is an end node of an entire device path.
  If Node represents the end of an entire device path, then TRUE is returned.
  Otherwise, FALSE is returned.
  If Node is NULL, then ASSERT().
  @param  Node      A pointer to a device path node data structure.
  @retval TRUE      The device path node specified by Node is the end of an entire device path.
  @retval FALSE     The device path node specified by Node is not the end of an entire device path.
**/
BOOLEAN
EFIAPI
IsDevicePathEnd (
  IN CONST VOID  *Node
  );
/**
  Returns a pointer to the next node in a device path.
  Returns a pointer to the device path node that follows the device path node specified by Node.
  If Node is NULL, then ASSERT().
  @param  Node      A pointer to a device path node data structure.
  @return a pointer to the device path node that follows the device path node specified by Node.
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
NextDevicePathNode (
  IN CONST VOID  *Node
  );

현재 Device Path에 있는 PCI 노드 수를 계산해보자.

EFI_DEVICE_PATH_PROTOCOL* TempDevicePathNode = PciDevicePathDynamicMulti;
UINT8 PciNodeCount = 0;
while (!IsDevicePathEnd(TempDevicePathNode)) {
  if ( (DevicePathType(TempDevicePathNode) == HARDWARE_DEVICE_PATH) && (DevicePathSubType(TempDevicePathNode) == HW_PCI_DP) )
    PciNodeCount++;
  TempDevicePathNode = NextDevicePathNode(TempDevicePathNode);
}
Print(L"Last device path has %d PCI nodes\n", PciNodeCount);

여기에서는 노드 필드에 액세스하는 간단한 함수인 DevicePathTypeDevicePathSubType 함수를 사용하였다.

이 코드를 실행하면 이전에 계산한 것과 동일한 결과를 볼 수 있다.

Last device path has 8 PCI nodes

텍스트에서 DevicePath/DeviceNode 만들기

이 레슨에서는 계속 ConvertDevicePathToText/ConvertDeviceNodeToText 함수를 사용하여 Device Path 와 노드를 텍스트 문자열로 변환했다.

그러나 실제로 라이브러리에서는 정반대의 방식을 나타내고 있다. 텍스트 문자열에서 Device Path/Device Node를 초기화할 수 있다. 이 작업을 위해 라이브러리에는 ConvertTextToDevicePathConvertTextToDeviceNode 함수가 존재한다.

/**
  Convert text to the binary representation of a device path.
  @param TextDevicePath  TextDevicePath points to the text representation of a device
                         path. Conversion starts with the first character and continues
                         until the first non-device node character.
  @return A pointer to the allocated device path or NULL if TextDeviceNode is NULL or
          there was insufficient memory.
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ConvertTextToDevicePath (
  IN CONST CHAR16  *TextDevicePath
  );
/**
  Convert text to the binary representation of a device node.
  @param TextDeviceNode  TextDeviceNode points to the text representation of a device
                         node. Conversion starts with the first character and continues
                         until the first non-device node character.
  @return A pointer to the EFI device node or NULL if TextDeviceNode is NULL or there was
          insufficient memory or text unsupported.
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ConvertTextToDeviceNode (
  IN CONST CHAR16  *TextDeviceNode
  );

UEFI 스펙을 확인할 필요가 없다. 문자열 표현에서 경로가 어떻게 보이는지 이미 알고 있으므로 우리의지식을 활용해보자.

EFI_DEVICE_PATH_PROTOCOL*  PciDevicePathNodeFromText = ConvertTextToDeviceNode(L"Pci(0x3,0x5)");
Print(L"PciDevicePathNodeFromText: %s\n", ConvertDeviceNodeToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathNodeFromText, FALSE, FALSE));
EFI_DEVICE_PATH_PROTOCOL*  PciDevicePathFromText = ConvertTextToDevicePath(L"Pci(0x3,0x5)");
Print(L"PciDevicePathFromText: %s\n", ConvertDevicePathToText((EFI_DEVICE_PATH_PROTOCOL*) PciDevicePathFromText, FALSE, FALSE));

코드를 실행해보면 예상되는 결과가 출력된다.

PciDevicePathNodeFromText: Pci(0x3,0x5)
PciDevicePathFromText: Pci(0x3,0x5)

할당된 메모리 해제하는 것 잊지 말기

이 섹션은 동적으로 생성된 Path 및 Node에 할당된 메모리를 해제하는 것을 잊지 말아야 하는 것을 다시 한번 상기 시키기 위해 만들었다.

FreePool(PciDevicePathNodeDynamic);
FreePool(PciDevicePathDynamic);
FreePool(PciDevicePathNodeFromText);
FreePool(PciDevicePathFromText);

Last updated