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.)

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

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 standalone UEFI driver from scratch

Having just come across a hardware random number generator called ChaosKey , I figured this would make an excellent candidate.

Create a build information file

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).

First of all, we need a build information file (). The format of the build information file is described in the, which can be found in the EDK2 github pages

[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 = ChaosKeyUnload

Start with a [Defines] section

0x0001.0x0019

Thereflects which version of the INF file format is being followed, in this case 1.25 (). Interestingly, nearly all build information files I have come across before specify 0x00010005 ... cargo culting from earlier examples.Followed by, 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, generated uniquely for this file - for example through this online generator , ensuringandare both ticked. If this string is copied from an existing template rather than uniquely generated,bad stuff will happen.And thento tell the build system we are producing a UEFI_DRIVER.is also required - this is simply a UCS2 string indicating the version of the driver.Thefunction 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.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.

# # VALID_ARCHITECTURES = AARCH64 ARM EBC IA32 IPF X64 #

MdePkg/MdePkg.dec

[Sources] ChaosKeyDriver.h DriverBinding.c [Packages] MdePkg/MdePkg.dec [LibraryClasses] UefiBootServicesTableLib UefiDriverEntryPoint UefiLib [Protocols] gEfiUsbIoProtocolGuid

Adding some actual code

The build information file usually includes a (commentary only) stanza stating which architectures the executable is expected to work on.The remainder of the file simply specifies which source files are used to build the driver, which declaration files it uses (), which library classes it needs (resolved into specific libraries by the build description file) and which protocols it consumes.

DriverBinding.c

EFI_DRIVER_BINDING_PROTOCOL

EntryPoint

Since we are not yet implementing any actual functionality beyond discovery, the only C source file added at this point is. This all comes down to implementing an instance of. Let us go through that, function by function.

gUsbDriverBinding

EFI_SUCCESS

gUsbDriverBinding

Supported()

Start()

Stop()

Version

Supported

All the entry point function does is register the protocol instance, as defined in thestruct, with the system - and return, printing an informational message as it does so. Thestruct contains pointers to theandfunctions defined by the protocol, as well as anumber which lets UEFI pick the most up to date driver if multiple are available.

EFI_UNSUPPORTED

EFI_SUCCESS

Start/Stop

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 their Supported() 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 returnsif this is a device it does not support, orif it is a device it supports.

Start()

Stop()

EFI_UNSUPPORTED

UnloadImage

andare left empty for now, returningwhenever they are called. This is something that will be filled in in part 2 of this series.

EntryPoint()

Building and using the driver

Building the standalone driver

Finally, when (if!) we unload the driver, UnloadImage() ensures that the bits that were registered byare unregistered again.

.dsc

.dsc

OptionRomPkg/OptionRomPkg.dsc

.inf

[Components]

OptionRomPkg.dsc

In order to build a standalone driver, you need a platform description () file, mapping your library dependencies to actually available libraries. One way of doing this is to implement your own complete. However, this is exactly what EDK2'sprovides. So a simpler way can be to simply add theto thesection of. 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

FS2:

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:\>

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 filesystem

drivers

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 0 Fv(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

At this point, you can verify that the driver has loaded by invoking thecommand:You can see how the driver is loaded, and has the driver handle number

FS2:\> ChaosKey (0x1D50:0x60C6) is my homeboy! UsbSelectConfig: failed to connect driver Not Found, ignored

Supported()

Start()

Unloading the driver

Now, plug in the ChaosKey:We can see how thefunction is invoked, and we then see an error message. The error is triggered by our emptyfunction returning EFI_UNSUPPORTED when UEFI attempts to bring the device online. We'll fix that bit in part 2 of the series.

unload

drivers

-v

FS2:\> unload -v 9E Revision......: 0x00001000 ParentHandle..: FD317918 SystemTable...: FDF30018 DeviceHandle..: FD243918 FilePath......: FC98FF98 OptionsSize...: 0 LoadOptions...: ImageBase.....: 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

During development, it can be handy to be able to load/unload without a full reboot. To do so, just callwith the driver handle you got from thecommand. Add theoption to get some extra output.You can also verify the unload was successful by runningagain, and seeing this driver no longer listed.