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 필드 값에 의해 지시되는 형식의 데이터 바로 뒤에 오게 된다.
#pragmapack(1)typedefstruct { 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 스펙에 정의되어 있다.
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 *EFIAPIConvertDeviceNodeToText ( IN CONST EFI_DEVICE_PATH_PROTOCOL *DeviceNode, IN BOOLEAN DisplayOnly, IN BOOLEAN AllowShortcuts );
이제 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 )
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 *EFIAPIConvertDevicePathToText ( IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath, IN BOOLEAN DisplayOnly, IN BOOLEAN AllowShortcuts );
실행해보면 다시 한 번 동일한 결과를 출력한다(중요한 문자열 표현이 없는 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 *EFIAPIAppendDevicePathNode ( 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를 효과적으로 생성한다.
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 노드를 더 추가해 보자.
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 *EFIAPIAppendDevicePath ( IN CONST EFI_DEVICE_PATH_PROTOCOL *FirstDevicePath OPTIONAL, IN CONST EFI_DEVICE_PATH_PROTOCOL *SecondDevicePath OPTIONAL );
AppendDevicePathNode/AppendDevicePath 함수의 본질을 잊으면 안된다. 모든 호출은 새 버퍼를 생성하지만 더 이상 필요하지 않은 이전 버퍼를 해제하는 것을 잊지 말아야한다. 그렇지 않으면 호출할 때마다 메모리 누수가 발생하게 된다. 따라서 더 정확한 코드는 다음과 같다.
라이브러리는 Device Paths를 반복하는 여러 함수를 제공한다. Device Path를 반복하는 유일한 방법은 Device End 노드를 찾을 때까지 Device Node를 하나씩 살펴보는 것이므로 라이브러리는 IsDevicePathEnd 및 NextDevicePathNode라는 두 가지 함수를 제공한다.
/** 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.**/BOOLEANEFIAPIIsDevicePathEnd ( 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 *EFIAPINextDevicePathNode ( IN CONST VOID *Node );
여기에서는 노드 필드에 액세스하는 간단한 함수인 DevicePathType 및 DevicePathSubType 함수를 사용하였다.
이 코드를 실행하면 이전에 계산한 것과 동일한 결과를 볼 수 있다.
Last device path has 8 PCI nodes
텍스트에서 DevicePath/DeviceNode 만들기
이 레슨에서는 계속 ConvertDevicePathToText/ConvertDeviceNodeToText 함수를 사용하여 Device Path 와 노드를 텍스트 문자열로 변환했다.
그러나 실제로 라이브러리에서는 정반대의 방식을 나타내고 있다. 텍스트 문자열에서 Device Path/Device Node를 초기화할 수 있다. 이 작업을 위해 라이브러리에는 ConvertTextToDevicePath 및 ConvertTextToDeviceNode 함수가 존재한다.
/** 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 *EFIAPIConvertTextToDevicePath ( 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 *EFIAPIConvertTextToDeviceNode ( IN CONST CHAR16 *TextDeviceNode );
UEFI 스펙을 확인할 필요가 없다. 문자열 표현에서 경로가 어떻게 보이는지 이미 알고 있으므로 우리의지식을 활용해보자.