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
현재 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하위 유형
전체 경로는 다음과 같이 인코딩된다.
EFI_DEVICE_PATH_PROTOCOL 구조를 풀면 다음과 같다.
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에 대한 코드는 다음과 같다.
또는 이와 같다.(사실상 기능은 동일하다.)
DevicePathLib에는 사람이 읽을 수 있는 텍스트 형식으로 DeviceNodes를 출력해주는 함수가 존재한다.
다음과 같이 Device Node를 출력하는데 사용하면,
아래와 같은 출력을 얻을 수 있다.
이제 Device Path 노드를 동적으로 생성해보자. 이를 위해 라이브러리에서 CreateDeviceNode함수를 사용할 수 있다.
코드는 다음과 같다.
그리고 이 코드는 우리에게 정확히 같은 결과를 출력해준다.
이제 완전한 Device Path를 생성해 보자. 이를 위해 생성한 현재 항목 바로 뒤에 End 유형의 Device Node를 추가해야 한다.
다시 한 번 정적 선언 방법으로 시작하자. PCI_DEVICE_PATH 노드와 End 노드를 모두 포함하는 자체 구조체 type을 만들어야한다.
Device Path를 문자열로 출력하려면 ConvertDevicePathToText 함수를 사용하면 된다.
이 함수는 ConvertDeviceNodeToText와 매우 유사하기 때문에 혼동하기 쉽다. Device Path가 아닌 Device Node에서 ConvertDevicePathToText를 수행하려고 하면 시스템이 중단될 수 있다.
이전처럼 간단하게 사용할 수 있다.
실행해보면 다시 한 번 동일한 결과를 출력한다(중요한 문자열 표현이 없는 End 노드만 추가했기 때문).
마지막으로 Device Path를 동적으로 생성해보자. 이 작업을 위해 AppendDevicePathNode 함수를 활용하면 된다.
주석에서 알 수 있듯이 첫 번째 인수 대신 NULL을 제공하면 함수는 필요한 Device Node End를 Device Node에 추가한다. 따라서 Device Node에서 Device Path를 효과적으로 생성한다.
출력하려는 경우 다음과 같은 코드를 쓸 수 있다.
또 한번 같은 결과를 출력한다.
다중 노드 Device Path
UEFI Shell을 부팅할때 자세히 보면 매핑을 위한 Device Path를 보여주는 매핑 테이블이 출력되는 것을 알 수 있다.
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 노드를 더 추가해 보자.
Print 문이 다음과 같이 출력할 것이라고 짐작할 수 있다.
AppendDevicePath 함수를 사용하여 두 개의 Device Path를 연결할 수도 있다.
이 함수는 Device Path 데이터 노드를 올바르게 연결하므로 최종 경로에는 하나의 Device Path End 노드만 존재한다.
우리의 경로에서 데이터 노드를 두 배로 늘리기 위해 이와 같이 사용할 수 있다.
그러면 4개가 아닌 8개의 PCI 노드가 제공된다.
AppendDevicePathNode/AppendDevicePath 함수의 본질을 잊으면 안된다. 모든 호출은 새 버퍼를 생성하지만 더 이상 필요하지 않은 이전 버퍼를 해제하는 것을 잊지 말아야한다. 그렇지 않으면 호출할 때마다 메모리 누수가 발생하게 된다. 따라서 더 정확한 코드는 다음과 같다.
Device Path 반복문
라이브러리는 Device Paths를 반복하는 여러 함수를 제공한다. Device Path를 반복하는 유일한 방법은 Device End 노드를 찾을 때까지 Device Node를 하나씩 살펴보는 것이므로 라이브러리는 IsDevicePathEnd 및 NextDevicePathNode라는 두 가지 함수를 제공한다.
현재 Device Path에 있는 PCI 노드 수를 계산해보자.
여기에서는 노드 필드에 액세스하는 간단한 함수인 DevicePathType 및 DevicePathSubType 함수를 사용하였다.
이 코드를 실행하면 이전에 계산한 것과 동일한 결과를 볼 수 있다.
텍스트에서 DevicePath/DeviceNode 만들기
이 레슨에서는 계속 ConvertDevicePathToText/ConvertDeviceNodeToText 함수를 사용하여 Device Path 와 노드를 텍스트 문자열로 변환했다.
그러나 실제로 라이브러리에서는 정반대의 방식을 나타내고 있다. 텍스트 문자열에서 Device Path/Device Node를 초기화할 수 있다. 이 작업을 위해 라이브러리에는 ConvertTextToDevicePath 및 ConvertTextToDeviceNode 함수가 존재한다.
UEFI 스펙을 확인할 필요가 없다. 문자열 표현에서 경로가 어떻게 보이는지 이미 알고 있으므로 우리의지식을 활용해보자.
코드를 실행해보면 예상되는 결과가 출력된다.
할당된 메모리 해제하는 것 잊지 말기
이 섹션은 동적으로 생성된 Path 및 Node에 할당된 메모리를 해제하는 것을 잊지 말아야 하는 것을 다시 한번 상기 시키기 위해 만들었다.
Last updated