34. 간단한 UEFI 드라이버 생성

이번 장에서는 UEFI 드라이버를 생성한다.

지금까지는 UEFI 애플리케이션만 만들었다. 애플리케이션과 드라이버의 주요 차이점은 애플리케이션이 실행된 후 메모리에 언로드된다는 점이다. 하지만 드라이버는 메모리에 남아있다. 또한 다른 애플리케이션이 사용할 유용한 프로토콜을 제공하기도 한다.

가장 간단한 드라이버로 UefiLessonsPkg/SimpleDriver/SimpleDriver.inf를 생성한다.

[Defines]
  INF_VERSION                    = 1.25
  BASE_NAME                      = SimpleDriver
  FILE_GUID                      = 384aeb18-105d-4af1-bf17-5e349e8f4d4c
  MODULE_TYPE                    = UEFI_DRIVER
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = SimpleDriverEntryPoint

[Sources]
  SimpleDriver.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiDriverEntryPoint
  UefiLib

기존에 애플리케이션을 생성할 때 사용했던 application.inf 파일과 드라이버를 생성할 때 사용하는 driver.inf 파일의 차이는 아래와 같다.

  • MODULE_TYPEUEFI_DRIVER이다. (이전에는 항상 UEFI_APPLICATION을 사용했다.

  • ENTRY_POINTSimpleDriverEntryPoint이다. (이전에는 항상 UefiMain을 사용했지만, 드라이버는 Entry/Unload 함수로 작동하므로 적절한 이름을 지정한다.)

  • UefiDriverEntryPoint 라이브러리 클래스가 사용된다. (이전에는 항상 UefiApplicationEntryPoint를 사용했다.) UefiApplicationEntryPoint 라이브러리에 대해 궁금하다면 아래 링크를 통해 확인할 수 있다. https://github.com/tianocore/edk2/tree/master/MdePkg/Library/UefiDriverEntryPoint

이제 UefiLessonsPkg/SimpleDriver/SimpleDriver.c 소스 파일을 생성한다. 구현해야 할 유일한 함수는 entry 함수인 SimpleDriverEntryPoint 이다.

#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

EFI_STATUS
EFIAPI
SimpleDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  Print(L"Hello from driver!\n");

  return EFI_SUCCESS;
}

드라이버를 생성하기 위해 UefiLessonsPkg/UefiLessonsPkg.dscComponents에 추가한다.

[Components]
  ...
  UefiLessonsPkg/SimpleDriver/SimpleDriver.inf

아직은빌드를 시도하면 아래와 같은 이유로 실패한다.

build.py...
/home/aladyshev/tiano/edk2/UefiLessonsPkg/UefiLessonsPkg.dsc(...): error 4000: Instance of library class [UefiDriverEntryPoint] is not found
        in [/home/aladyshev/tiano/edk2/UefiLessonsPkg/SimpleDriver/SimpleDriver.inf] [X64]
        consumed by module [/home/aladyshev/tiano/edk2/UefiLessonsPkg/SimpleDriver/SimpleDriver.inf]

애플리케이션을 만들 때와 마찬가지로 필요한 라이브러리를 찾아야 한다. 2개의 결과가 나오지만 필요한 라이브러리는 MdePkg의 것이므로 이것을 사용한다.

$ grep UefiDriverEntryPoint -r ./ --exclude-dir=Build | grep LIBRARY_CLASS
./MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf:  LIBRARY_CLASS                  = UefiDriverEntryPoint|DXE_DRIVER DXE_RUNTIME_DRIVER UEFI_DRIVER SMM_CORE DXE_SMM_DRIVER

확인한 UefiDriverEntryPoint.int 파일의 경로를 UefiLessonsPkg/UefiLessonsPkg.dsc에 추가한다.

[LibraryClasses]
  ...
  UefiDriverEntryPoint|MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf

빌드 후 SimpleDriver.efi를 실행하면 아래와 같이 애플리케이션이 아니라는 메시지를 확인할 수 있다.

FS0:\> SimpleDriver.efi
The image is not an application.

위 메시지를 통해서 알 수 있듯이 드라이버는 실행이 불가능하다. 그래서 드라이버를 로드하기 위해서 Shell에서 load명령어를 사용해야 한다.

FS0:\> load -? -b
Loads a UEFI driver into memory.

LOAD [-nc] file [file...]

  -nc  - Loads the driver, but does not connect the driver.
  File - Specifies a file that contains the image of the UEFI driver (wildcards are
         permitted).

NOTES:
  1. This command loads a driver into memory. It can load multiple files at
     one time. The file name supports wildcards.
  2. If the -nc flag is not specified, this command attempts to connect the
     driver to a proper device. It might also cause previously loaded drivers
     to be connected to their corresponding devices.
  3. Use the 'UNLOAD' command to unload a driver.

EXAMPLES:
  * To load a driver:
    fs0:\> load Isabus.efi

  * To load multiple drivers:
    fs0:\> load Isabus.efi IsaSerial.efi

  * To load multiple drivers using file name wildcards:
    fs0:\> load Isa*.efi

  * To load a driver without connecting it to a device:
    fs0:\> load -nc IsaBus.efi

load 명령어를 사용해 드라이버를 로드한다.

FS0:\> load SimpleDriver.efi
Hello from driver!
Image 'FS0:\SimpleDriver.efi' loaded at 6646000 - Success

이제 dh 명령어로 드라이버 핸들을 살펴본다.

FS0:\> dh -? -b
Displays the device handles in the UEFI environment.

DH [-l <lang>] [handle | -p <prot_id>] [-d] [-v]

  -p     - Dumps all handles of a protocol specified by the GUID.
  -d     - Dumps UEFI Driver Model-related information.
  -l     - Dumps information using the language codes (e.g. ISO 639-2).
  -sfo   - Displays information as described in Standard-Format Output.
  -v     - Dumps verbose information about a specific handle.
  handle - Specifies a handle to dump information about (a hexadecimal number).
           If not present, then all information will be dumped.

NOTES:
  1. When neither 'handle' nor 'prot_id' is specified, a list of all the
     device handles in the UEFI environment is displayed.
  2. The '-d' option displays UEFI Driver Model related information including
     parent handles, child handles, all drivers installed on the handle, etc.
  3. The '-v' option displays verbose information for the specified handle
     including all the protocols on the handle and their details.
  4. If the '-p' option is specified, all handles containing the specified
     protocol will be displayed. Otherwise, the 'handle' parameter has to be
     specified for display. In this case, the '-d' option will be enabled
     automatically if the '-v' option is not specified.

EXAMPLES:
  * To display all handles and display one screen at a time:
    Shell> dh -b

  * To display the detailed information on handle 0x30:
    Shell> dh 30

  * To display all handles with 'diskio' protocol:
    Shell> dh -p diskio

  * To display all handles with 'LoadedImage' protocol and break when the screen is
    full:
    Shell> dh -p LoadedImage -b

매개변수 없이 dh 명령어를 실행하면 시스템의 모든 핸들을 얻을 수 있다. 우리가 만든 드라이버는 맨 마지막에 확인할 수 있다.

FS0:\> dh
...
C0: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)

핸들을 더 상세하게 확인할 수도 있다.

FS0:\> dh -d -v c0
C6: 664C998
ImageDevicePath(664A018)
  PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\SimpleDriver.efi
LoadedImage(664A440)
  Revision......: 0x00001000
  ParentHandle..: 6EE5D18
  SystemTable...: 79EE018
  DeviceHandle..: 6E36798
  FilePath......: \SimpleDriver.efi
  PdbFileName...: /home/aladyshev/tiano/edk2/Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/SimpleDriver/SimpleDriver/DEBUG/SimpleDriver.dll
  OptionsSize...: 0
  LoadOptions...: 0
  ImageBase.....: 6646000
  ImageSize.....: 16C0
  CodeType......: EfiBootServicesCode
  DataType......: EfiBootServicesData
  Unload........: 0

생성한 드라이버로 더 많은 인스턴스를 생성할 수 있으며, 많이 생성한다고 해서 문제되지 않는다.

FS0:\> load SimpleDriver.efi
Hello from driver!
Image 'FS0:\SimpleDriver.efi' loaded at 6619000 - Success
FS0:\> load SimpleDriver.efi
Hello from driver!
Image 'FS0:\SimpleDriver.efi' loaded at 6617000 - Success
FS0:\> load SimpleDriver.efi
Hello from driver!
Image 'FS0:\SimpleDriver.efi' loaded at 6613000 - Success

dh 명령어를 통해 확인하면 드라이버 핸들이 여러 개인 것을 확인할 수 있다.

FS0:\> dh
...
C0: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)
C1: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)
C2: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)
C3: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)

메모리에서 드라이버를 언로드하려면 unload 명령을 사용하면 된다.

FS0:\> unload -?
Unloads a driver image that was already loaded.

UNLOAD [-n] [-v|-verbose] Handle

  -n           - Skips all prompts during unloading, so that it can be used
                 in a script file.
  -v, -verbose - Dumps verbose status information before the image is unloaded.
  Handle       - Specifies the handle of driver to unload, always taken as hexadecimal number.

NOTES:
  1. The '-n' option can be used to skip all prompts during unloading.
  2. If the '-v' option is specified, verbose image information will be
     displayed before the image is unloaded.
  3. Only drivers that support unloading can be successfully unloaded.
  4. Use the 'LOAD' command to load a driver.

EXAMPLES:
  * To find the handle for the UEFI driver image to unload:
    Shell> dh -b

  * To unload the UEFI driver image with handle 27:
    Shell> unload 27

unload 명령은 드라이버에 Unload 함수가 없기 때문에 지금은 사용할 수 없다. dh -d -v c0을 사용하면 Unload........: 0 부분을 확인할 수 있다.

따라서 드라이버를 언로드하려고 하면 오류가 발생한다.

FS0:\> unload c0
Unload - Handle [664C998].  [y/n]?
y
Unload - Handle [664C998] Result Unsupported.

dh 명령어를 실행하면 시스템에 드라이버 핸들이 여전히 존재하는 것을 볼 수 있다.

Unload 함수 추가

이제 언로드 함수를 드라이버에 추가한다. 아래 설정을 UefiLessonsPkg/SimpleDriver/SimpleDriver.inf에 추가한다.

[Defines]
  ...
  ENTRY_POINT                    = SimpleDriverEntryPoint
+ UNLOAD_IMAGE                   = SimpleDriverUnload

UefiLessonsPkg/SimpleDriver/SimpleDriver.c에 함수를 추가한다.

EFI_STATUS
EFIAPI
SimpleDriverUnload (
  EFI_HANDLE ImageHandle
  )
{
  Print(L"Bye-bye from driver!\n");

  return EFI_SUCCESS;
}

새로 빌드하고 드라이버를 로드한 후 dh -d -v 명령어를 사용하면 위에서 확인한 결과와는 달리 Unload에 값이 들어간 것을 볼 수 있다.

FS0:\> dh -d -v c0
C6: 664CA98
ImageDevicePath(664C618)
  PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)/\SimpleDriver.efi
LoadedImage(664A240)
  Revision......: 0x00001000
  ParentHandle..: 6EE5D18
  SystemTable...: 79EE018
  DeviceHandle..: 6E36798
  FilePath......: \SimpleDriver.efi
  PdbFileName...: /home/aladyshev/tiano/edk2/Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/SimpleDriver/SimpleDriver/DEBUG/SimpleDriver.dll
  OptionsSize...: 0
  LoadOptions...: 0
  ImageBase.....: 6646000
  ImageSize.....: 1780
  CodeType......: EfiBootServicesCode
  DataType......: EfiBootServicesData
  Unload........: 6647047

출력된 결과를 통해 알 수 있듯이 언로드 문자열이 드라이버 언로드 함수에 대한 포인터로 채워진다.

드라이버를 언로드하기 전에 dmem을 통해 ImageBase 주소를 확인한다.

FS0:\> dmem 6646000 A0
Memory Address 0000000006646000 A0 Bytes
  06646000: 4D 5A 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *MZ..............*
  06646010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  06646020: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  06646030: 00 00 00 00 00 00 00 00-00 00 00 00 80 00 00 00  *................*
  06646040: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  06646050: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  06646060: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  06646070: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  06646080: 50 45 00 00 64 86 02 00-00 00 00 00 00 00 00 00  *PE..d...........*
  06646090: 00 00 00 00 F0 00 2E 00-0B 02 00 00 40 14 00 00  *............@...*

MZ 시그니처는 PE/COFF 이미지(*.efi 파일)의 헤더를 나타낸다. 그래서 실제 드라이버는 ImageBase에 존재한다.

이제 드라이버를 언로드 한다.

FS0:\> unload c6
Unload - Handle [664CF18].  [y/n]?
y
Bye-bye from driver!
Unload - Handle [664CF18] Result Success.

메모리를 다시 확인하면 아래와 같이 비어진 것을 확인할 수 있다.

FS0:\> dmem 6646000 A0
Memory Address 0000000006646000 A0 Bytes
  06646000: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646010: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646020: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646030: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646040: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646050: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646060: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646070: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646080: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*
  06646090: AF AF AF AF AF AF AF AF-AF AF AF AF AF AF AF AF  *................*

드라이버를 새로 로드하면 기존에 사용했던 C0은 넘어가고 C1으로 핸들이 존재하는 것을 볼 수 있다.

FS0:\> dh
...
C7: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)

Last updated