UEFI Driver Development - Part 1
One of the key features of UEFI is that the specification and the APIs/ABIs it guarantees provides the ability to produce portable applications, drivers and libraries (in the form of protocols). On the simpler side, by letting you compile the driver once for each architecture - and on the more space age side by letting you build a single driver that works across all architectures (using EFI Byte Code). The extra magic comes in the form of Option ROM support, which lets plug-in PCI cards keep a driver onboard, informing UEFI to load it on boot. (Any jokes about Forth drivers for Open Firmware go here.) So, having never actually written a UEFI driver from scratch, and most of the drivers I have come across having really been platform drivers, I figured that would be a good start to write a standalone driver from scratch. And the outcome is this slightly hands-on blog post series. This part covers:- creating a new driver from scratch
- building it as a standalone driver
- loading it from the UEFI Shell
- having it detect the presence of a device it recognizes
- unloading it from the UEFI Shell
Creating a standalone UEFI driver from scratch
Since UEFI drivers are meant to be standalone buildable, they tend to be kept in separate directories. Since this driver is intended to run during/after DXE phase, let's put everything in a directory called ChaosKeyDxe (CamelCase mandatory - link takes you to the directory in git, where you can view the files directly).Create a build information file
First of all, we need a build information file (.inf). The format of the build information file is described in the EDK II INF File Spec, which can be found in the EDK2 github pages. Start with a [Defines] section[Defines] INF_VERSION = 0x00010019 BASE_NAME = ChaosKeyDxe FILE_GUID = 9A54122B-F5E4-40D8-AE61-A71E406ED449 MODULE_TYPE = UEFI_DRIVER VERSION_STRING = 1.0 ENTRY_POINT = ChaosKeyEntryPoint UNLOAD_IMAGE = ChaosKeyUnloadThe INF_VERSION reflects which version of the INF file format is being followed, in this case 1.25 (
0x0001.0x0019
). Interestingly, nearly all build information files I have come across before specify 0x00010005 ... cargo culting from earlier examples.Followed by BASE_NAME, the single word identifier used for this component. To keep things simple, I'm reusing the directory name, for the same reasons.
And then a FILE_GUID, generated uniquely for this file - for example through this online generator, ensuring Uppcase and Hyphens are both ticked. If this string is copied from an existing template rather than uniquely generated, really bad stuff will happen.
And then MODULE_TYPE to tell the build system we are producing a UEFI_DRIVER.
A VERSION_STRING is also required - this is simply a UCS2 string indicating the version of the driver.
The ENTRY_POINT function is automatically called at driver load time, and needs to contain code that registers the driver with the system, and do any other global setup needed to make the driver ready to set up individual devices.
UNLOAD_IMAGE points to the function cleaning up after the driver when it is to be unloaded. This is not really mandatory for a driver expected to be used for booting the system (it will be discarded by the operating system anyway, unless it takes specific actions to keep bits resident), but it comes in very handy for development. The build information file usually includes a (commentary only) stanza stating which architectures the executable is expected to work on.
# # VALID_ARCHITECTURES = AARCH64 ARM EBC IA32 IPF X64 #The remainder of the file simply specifies which source files are used to build the driver, which declaration files it uses (
MdePkg/MdePkg.dec
), which library classes it needs (resolved into specific libraries by the build description file) and which protocols it consumes.
[Sources] ChaosKeyDriver.h DriverBinding.c [Packages] MdePkg/MdePkg.dec [LibraryClasses] UefiBootServicesTableLib UefiDriverEntryPoint UefiLib [Protocols] gEfiUsbIoProtocolGuid
Adding some actual code
Since we are not yet implementing any actual functionality beyond discovery, the only C source file added at this point isDriverBinding.c
. This all comes down to implementing an instance of EFI_DRIVER_BINDING_PROTOCOL
. Let us go through that, function by function.
EntryPoint
All the entry point function does is register the protocol instance, as defined in thegUsbDriverBinding
struct, with the system - and return EFI_SUCCESS
, printing an informational message as it does so. The gUsbDriverBinding
struct contains pointers to the Supported()
, Start()
and Stop()
functions defined by the protocol, as well as a Version
number which lets UEFI pick the most up to date driver if multiple are available.
Supported
When a new device is detected in the system, UEFI will ask all of the plausible drivers whether they know how to deal with it, by calling theirSupported()
function. This implementation is probably one of the few bits of this driver that is pretty much feature complete - all it really needs to do is to find the USB manufacturer/device IDs and see if they are ones the driver knows how to handle. It then returns EFI_UNSUPPORTED
if this is a device it does not support, or EFI_SUCCESS
if it is a device it supports.
Start/Stop
Start()
and Stop()
are left empty for now, returning EFI_UNSUPPORTED
whenever they are called. This is something that will be filled in in part 2 of this series.
UnloadImage
Finally, when (if!) we unload the driver,UnloadImage()
ensures that the bits that were registered by EntryPoint()
are unregistered again.
Building and using the driver
Building the standalone driver
In order to build a standalone driver, you need a platform description (.dsc
) file, mapping your library dependencies to actually available libraries. One way of doing this is to implement your own complete .dsc
. However, this is exactly what EDK2's OptionRomPkg/OptionRomPkg.dsc
provides. So a simpler way can be to simply add the .inf
to the [Components]
section of OptionRomPkg.dsc
. After that, the build should be as easy as:
GCC5_AARCH64_PREFIX=aarch64-linux-gnu- build -a AARCH64 -t GCC5 -p OptionRomPkg/OptionRomPkg.dsc -m OpenPlatformPkg/Drivers/Usb/Misc/ChaosKeyDxe/ChaosKeyDxe.inf
Loading the driver
For this example (and because Juno's built-in magical program-over-USB filesystem is insane), let's load the driver from a USB key. For simplicity's sake, have it plugged in when powering on and drop into the UEFI Shell. In my case, the USB key ended up as filesystemFS2:
.
Shell> FS2: FS2:\> load ChaosKeyDxe.efi add-symbol-file /home/leif/work/git/edk2/Build/OptionRomPkg/DEBUG_GCC5/AARCH64/OpenPlatformPkg/Drivers/Usb/Misc/ChaosKeyDxe/ChaosKeyDxe/DEBUG/ChaosKeyDxe.dll 0xF85E4000 Loading driver at 0x000F85E3000 EntryPoint=0x000F85E4044 ChaosKeyDxe.efi *** Installed ChaosKey driver! *** Image 'FS2:\ChaosKeyDxe.efi' loaded at F85E3000 - Success FS2:\>At this point, you can verify that the driver has loaded by invoking the
drivers
command:
FS2:\> drivers T D Y C I P F A DRV VERSION E G G #D #C DRIVER NAME IMAGE PATH === ======== = = = === === =================================== ========== 23 00000030 D N N 1 0You can see how the driver is loaded, and has the driver handle numberFv(B73FE497-B92E-416E-8326-45AD0D270092)/FvFile(1DF18DA0-A18B-11DF-8C3A-0002A5D5C51B) ... 6F 0000000A D N N 1 0 Fv(B73FE497-B92E-416E-8326-45AD0D270092)/FvFile(4579B72D-7EC4-4DD4-8486-083C86B182A7) 9E 0000000A ? N N 0 0 FS2:\ChaosKeyDxe.efi
9E
.
Now, plug in the ChaosKey:
FS2:\> ChaosKey (0x1D50:0x60C6) is my homeboy! UsbSelectConfig: failed to connect driver Not Found, ignoredWe can see how the
Supported()
function is invoked, and we then see an error message. The error is triggered by our empty Start()
function returning EFI_UNSUPPORTED when UEFI attempts to bring the device online. We'll fix that bit in part 2 of the series.
Unloading the driver
During development, it can be handy to be able to load/unload without a full reboot. To do so, just callunload
with the driver handle you got from the drivers
command. Add the -v
option to get some extra output.
FS2:\> unload -v 9E Revision......: 0x00001000 ParentHandle..: FD317918 SystemTable...: FDF30018 DeviceHandle..: FD243918 FilePath......: FC98FF98 OptionsSize...: 0 LoadOptions...:You can also verify the unload was successful by runningImageBase.....: F85E3000 ImageSize.....: 8000 CodeType......: EfiBootServicesCode DataType......: EfiBootServicesData Unload........: F85E4000 Unload - Handle [FC990598]. [y/n]? y remove-symbol-file /home/leif/work/git/edk2/Build/OptionRomPkg/DEBUG_GCC5/AARCH64/OpenPlatformPkg/Drivers/Usb/Misc/ChaosKeyDxe/ChaosKeyDxe/DEBUG/ChaosKeyDxe.dll 0xF85E4000 Unload - Handle [FC990598] Result Success. FS2:\>
drivers
again, and seeing this driver no longer listed.
posted at: 22:02 | path: /uefi | permanent link to this entry