Let's create a simple UEFI driver.
Up until now we've created only UEFI applications. The main difference between application and a driver is a fact that application is unloaded from the memory after its execution. But the driver is a thing that stays in memory. And while it stays there it can provide usefull protocols for other applications to use.
Let's create a simplest driver 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
Things that have changed from our usual INF file:
- The
MODULE_TYPE
isUEFI_DRIVER
(earlier we've always usedUEFI_APPLICATION
), - The
ENTRY_POINT
isSimpleDriverEntryPoint
(earlier we've always usedUefiMain
, but the driver operates with Entry/Unload functions, so it is better to start to give them proper names), UefiDriverEntryPoint
library class is used (earlier we've always usedUefiApplicationEntryPoint
). In case you wonder aboutUefiDriverEntryPoint
library internals take a look into https://github.com/tianocore/edk2/tree/master/MdePkg/Library/UefiDriverEntryPoint
Now let's write *.c source file UefiLessonsPkg/SimpleDriver/SimpleDriver.c
The only function that we need to implement is our entry function SimpleDriverEntryPoint
that we've declared:
#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;
}
Include this driver in the components section of our UefiLessonsPkg/UefiLessonsPkg.dsc
:
[Components]
...
UefiLessonsPkg/SimpleDriver/SimpleDriver.inf
If you try to build, the process would fail:
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]
As usually we need to find proper library implementation:
$ 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
And place it in our DSC:
[LibraryClasses]
...
UefiDriverEntryPoint|MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf
Build, copy file to our QEMU shared folder and run OVMF.
First let's try to execute it as an app:
FS0:\> SimpleDriver.efi
The image is not an application.
As you see this action is not possible.
To load driver we need to use load
shell command:
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
Use this command to load our driver:
FS0:\> load SimpleDriver.efi
Hello from driver!
Image 'FS0:\SimpleDriver.efi' loaded at 6646000 - Success
Now let's try to use dh
command to look at our driver handle:
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
If you'll execute it without any parameters, you'll get all handles in the system. And our driver would be the last one:
FS0:\> dh
...
C6: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)
You can print more verbose output for our handle:
FS0:\> dh -d -v c6
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
You can load more instanses of our driver, this is not a problem:
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
If you look at dh now, there would be multiple handles from our driver:
FS0:\> dh
...
C6: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)
C7: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)
C8: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)
C9: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)
To unload driver from memory you can utilize unload
command:
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
But it is now possible to use it now as our driver don't have an Unload function. If you'll look at the earlier output of dh -d -v c6
command, you can see that Unload........: 0
.
Therefore if you'll try to unload our driver you'll get an error:
FS0:\> unload c6
Unload - Handle [664C998]. [y/n]?
y
Unload - Handle [664C998] Result Unsupported.
If you'll execute dh
, you would still see that our driver handle is still present in the system.
Now let's try add unload function to our driver. Add it to the INF file UefiLessonsPkg/SimpleDriver/SimpleDriver.inf:
[Defines]
...
ENTRY_POINT = SimpleDriverEntryPoint
+ UNLOAD_IMAGE = SimpleDriverUnload
And add some simple implementation to the *.c file UefiLessonsPkg/SimpleDriver/SimpleDriver.c:
EFI_STATUS
EFIAPI
SimpleDriverUnload (
EFI_HANDLE ImageHandle
)
{
Print(L"Bye-bye from driver!\n");
return EFI_SUCCESS;
}
If you try to execute dh -d -v
on the handle from this driver you'll get something like:
FS0:\> dh -d -v c6
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
As you can see now Unload
string is filled with a pointer to the driver unload function.
Before preforming an unload take a look at the ImageBase address with dmem
:
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
signature signifies the header of a PE/COFF image (*.efi file). So our driver is actually there.
Now perform unload:
FS0:\> unload c6
Unload - Handle [664CF18]. [y/n]?
y
Bye-bye from driver!
Unload - Handle [664CF18] Result Success.
Look at the memory again:
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 *................*
As you can see in was automatically freed.
One more notice. If you'll load your image again, it would have a handle with C7 number. Number C6 would be skipped:
C5: ...
C7: ImageDevicePath(..F,0xFBFC1)/\SimpleDriver.efi) LoadedImage(\SimpleDriver.efi)